Skip to content
Vitest 1
Main Navigation РуководствоAPIКонфигурацияПродвинутый
1.6.1
0.34.6

Русский

English
简体中文
繁體中文
Español
Français
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

Русский

English
简体中文
繁體中文
Español
Français
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

Внешний вид

Sidebar Navigation

Руководство

Зачем Vitest

Начало работы

Функциональность

Рабочая область

Интерфейс командной строки

Фильтрация тестов

Репортёры

Покрытие кода

Снапшоты

Мокирование

Тестирование типов

Vitest UI

Режим браузера

In-source тестирование (Тестирование в исходном коде)

Контекст теста

Тестовая среда

Расширение проверок (matchers)

Интеграции с IDE

Отладка

Сравнения с другими тестовыми фреймворками

Руководство по миграции

Распространенные ошибки

Улучшение производительности

API

Справочник по Test API

Mock-функции

Vitest

expect

expectTypeOf

assert

assertType

Конфигурация

Настройка конфигурационного файла Vitest

Настройка Vitest

Содержание страницы

Мокирование ​

При написании тестов часто возникает необходимость в создании имитации (мока) внутреннего или внешнего сервиса. Это называется мокированием. Vitest предоставляет набор вспомогательных функций для упрощения мокирования, доступных через хелпер vi. Вы можете import { vi } from 'vitest' или получить к нему доступ глобально (если глобальная конфигурация включена).

WARNING

Всегда помните о необходимости очистки или восстановления моков до или после каждого запуска теста, чтобы отменить изменения состояния моков между запусками! Смотрите документацию mockReset для получения дополнительной информации.

Для быстрого ознакомления с API, перейдите в соответствующий раздел. В противном случае, продолжайте чтение для более детального изучения мокирования.

Даты ​

Иногда требуется контролировать дату, чтобы обеспечить согласованность при тестировании. Vitest использует пакет @sinonjs/fake-timers для манипулирования таймерами, а также системной датой. Подробнее об API можно узнать здесь.

Пример ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const businessHours = [9, 17];

function purchase() {
  const currentHour = new Date().getHours();
  const [open, close] = businessHours;

  if (currentHour > open && currentHour < close) return { message: 'Success' };

  return { message: 'Error' };
}

describe('purchasing flow', () => {
  beforeEach(() => {
    // tell vitest we use mocked time
    vi.useFakeTimers();
  });

  afterEach(() => {
    // restoring date after each test run
    vi.useRealTimers();
  });

  it('allows purchases within business hours', () => {
    // set hour within business hours
    const date = new Date(2000, 1, 1, 13);
    vi.setSystemTime(date);

    // access Date.now() will result in the date set above
    expect(purchase()).toEqual({ message: 'Success' });
  });

  it('disallows purchases outside of business hours', () => {
    // set hour outside business hours
    const date = new Date(2000, 1, 1, 19);
    vi.setSystemTime(date);

    // access Date.now() will result in the date set above
    expect(purchase()).toEqual({ message: 'Error' });
  });
});

Функции ​

Мокирование функций можно разделить на две категории: шпионаж и мокирование.

Иногда достаточно проверить, была ли вызвана функция, и, возможно, с какими аргументами. В этих случаях шпионажа будет достаточно, и вы можете использовать vi.spyOn() (подробнее здесь).

Однако шпионы позволяют только отслеживать вызовы функций, но не изменять их реализацию. В случае, когда нам нужно создать фейковую (или мокированную) версию функции, мы можем использовать vi.fn() (подробнее здесь).

Мы используем Tinyspy в качестве основы для мокирования функций, но у нас есть собственная обертка, чтобы сделать его совместимым с jest. И vi.fn(), и vi.spyOn() имеют общие методы, однако только результат, возвращаемый vi.fn(), можно вызывать.

Пример ​

js
import { afterEach, describe, expect, it, vi } from 'vitest';

function getLatest(index = messages.items.length - 1) {
  return messages.items[index];
}

const messages = {
  items: [
    { message: 'Simple test message', from: 'Testman' },
    // ...
  ],
  getLatest, // can also be a `getter or setter if supported`
};

describe('reading messages', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should get the latest message with a spy', () => {
    const spy = vi.spyOn(messages, 'getLatest');
    expect(spy.getMockName()).toEqual('getLatest');

    expect(messages.getLatest()).toEqual(
      messages.items[messages.items.length - 1]
    );

    expect(spy).toHaveBeenCalledTimes(1);

    spy.mockImplementationOnce(() => 'access-restricted');
    expect(messages.getLatest()).toEqual('access-restricted');

    expect(spy).toHaveBeenCalledTimes(2);
  });

  it('should get with a mock', () => {
    const mock = vi.fn().mockImplementation(getLatest);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(1);

    mock.mockImplementationOnce(() => 'access-restricted');
    expect(mock()).toEqual('access-restricted');

    expect(mock).toHaveBeenCalledTimes(2);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(3);
  });
});

Больше информации ​

  • Jest's Mock Functions

Глобальные переменные ​

Вы можете мокировать глобальные переменные, отсутствующие в jsdom или node, используя хелпер vi.stubGlobal. Он добавит значение глобальной переменной в объект globalThis.

ts
import { vi } from 'vitest';

const IntersectionObserverMock = vi.fn(() => ({
  disconnect: vi.fn(),
  observe: vi.fn(),
  takeRecords: vi.fn(),
  unobserve: vi.fn(),
}));

vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);

// now you can access it as `IntersectionObserver` or `window.IntersectionObserver`

Модули ​

Мокирование модулей позволяет отслеживать сторонние библиотеки, используемые в коде, и тестировать передаваемые аргументы, возвращаемые значения или даже переопределять их поведение.

Смотрите раздел API vi.mock() api section для более подробного описания API.

Алгоритм автоматического мокирования ​

Если импортируется модуль, для которого не определены файлы __mocks__ или factory, Vitest автоматически создаст мок для этого модуля, имитируя его вызов и мокируя каждый экспорт.

Применяются следующие принципы:

  • Все массивы будут очищены.
  • Все примитивы и коллекции останутся без изменений.
  • Все объекты будут глубоко клонированы.
  • Все экземпляры классов и их прототипы будут глубоко клонированы.

Виртуальные модули ​

Vitest поддерживает имитацию виртуальных модулей Vite. Это работает иначе, чем обработка виртуальных модулей в Jest. Вместо передачи virtual: true в функцию vi.mock, вам необходимо сообщить Vite о существовании модуля, иначе возникнет ошибка во время анализа. Это можно сделать несколькими способами:

  1. Предоставьте алиас
ts
// vitest.config.js
export default {
  test: {
    alias: {
      '$app/forms': resolve('./mocks/forms.js'),
    },
  },
};
  1. Предоставьте плагин, который разрешает виртуальный модуль
ts
// vitest.config.js
export default {
  plugins: [
    {
      name: 'virtual-modules',
      resolveId(id) {
        if (id === '$app/forms') return 'virtual:$app/forms';
      },
    },
  ],
};

Преимущество второго подхода заключается в том, что вы можете динамически создавать различные виртуальные точки входа. Если вы перенаправляете несколько виртуальных модулей в один файл, то все они будут затронуты vi.mock, поэтому используйте уникальные идентификаторы.

Типичные ошибки при имитации ​

Имейте в виду, что невозможно имитировать вызовы методов, которые вызываются внутри других методов того же файла. Например, в этом коде:

ts
export function foo() {
  return 'foo';
}

export function foobar() {
  return `${foo()}bar`;
}

Невозможно имитировать метод foo извне, поскольку на него есть прямая ссылка. Таким образом, этот код не повлияет на вызов foo внутри foobar (но повлияет на вызов foo в других модулях):

ts
import { vi } from 'vitest';
import * as mod from './foobar.js';

// это повлияет только на "foo" вне исходного модуля
vi.spyOn(mod, 'foo');
vi.mock('./foobar.js', async importOriginal => {
  return {
    ...(await importOriginal<typeof import('./foobar.js')>()),
    // это повлияет только на "foo" вне исходного модуля
    foo: () => 'mocked',
  };
});

Вы можете подтвердить это поведение, предоставив реализацию методу foobar напрямую:

ts
// foobar.test.js
import * as mod from './foobar.js';

vi.spyOn(mod, 'foo');

// экспортированный foo ссылается на имитированный метод
mod.foobar(mod.foo);
ts
// foobar.js
export function foo() {
  return 'foo';
}

export function foobar(injectedFoo) {
  return injectedFoo !== foo; // false
}

Это предполагаемое поведение. Обычно это признак плохого кода, когда имитация используется таким образом. Рассмотрите возможность рефакторинга кода на несколько файлов или улучшения архитектуры приложения с использованием таких методов, как внедрение зависимостей.

Пример ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';

// handlers
export function success(data) {}
export function failure(data) {}

// get todos
export async function getTodos(event, context) {
  const client = new Client({
    // ...clientOptions
  });

  await client.connect();

  try {
    const result = await client.query('SELECT * FROM todos;');

    client.end();

    return success({
      message: `${result.rowCount} item(s) returned`,
      data: result.rows,
      status: true,
    });
  } catch (e) {
    console.error(e.stack);

    client.end();

    return failure({ message: e, status: false });
  }
}

vi.mock('pg', () => {
  const Client = vi.fn();
  Client.prototype.connect = vi.fn();
  Client.prototype.query = vi.fn();
  Client.prototype.end = vi.fn();

  return { Client };
});

vi.mock('./handlers.js', () => {
  return {
    success: vi.fn(),
    failure: vi.fn(),
  };
});

describe('get a list of todo items', () => {
  let client;

  beforeEach(() => {
    client = new Client();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('should return items successfully', async () => {
    client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);

    expect(success).toBeCalledWith({
      message: '0 item(s) returned',
      data: [],
      status: true,
    });
  });

  it('should throw an error', async () => {
    const mError = new Error('Unable to retrieve rows');
    client.query.mockRejectedValueOnce(mError);

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);
    expect(failure).toBeCalledWith({ message: mError, status: false });
  });
});

Запросы ​

Поскольку Vitest работает в Node.js, мокирование сетевых запросов может быть затруднительным из-за отсутствия веб-API. Поэтому требуется инструмент, имитирующий сетевое поведение. Мы рекомендуем Mock Service Worker для этой цели. Он позволит вам мокировать как REST, так и GraphQL сетевые запросы и является фреймворк-агностиком.

Mock Service Worker (MSW) работает путем перехвата запросов, которые делают ваши тесты, позволяя вам использовать его без изменения кода вашего приложения. В браузере он использует Service Worker API. В Node.js и для Vitest он использует @mswjs/interceptors. Чтобы узнать больше о MSW, прочитайте их введение.

Конфигурация ​

Пример использования в файле конфигурации:

js
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { HttpResponse, graphql, http } from 'msw';

const posts = [
  {
    userId: 1,
    id: 1,
    title: 'first post title',
    body: 'first post body',
  },
  // ...
];

export const restHandlers = [
  http.get('https://rest-endpoint.example/path/to/posts', () => {
    return HttpResponse.json(posts);
  }),
];

const graphqlHandlers = [
  graphql.query('ListPosts', () => {
    return HttpResponse.json({
      data: { posts },
    });
  }),
];

const server = setupServer(...restHandlers, ...graphqlHandlers);

// Start server before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

//  Close server after all tests
afterAll(() => server.close());

// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers());

Конфигурация сервера с опцией onUnhandleRequest: 'error' гарантирует генерацию ошибки при каждом запросе, для которого не определен обработчик.

Пример ​

У нас есть полный рабочий пример, который использует MSW: React Testing with MSW.

Больше информации ​

В MSW есть гораздо больше возможностей. Вы можете получить доступ к файлам cookie и параметрам запроса, определять фиктивные ответы об ошибках и многое другое! Чтобы увидеть все, что вы можете делать с MSW, прочитайте их документацию.

Таймеры ​

При тестировании кода, использующего тайм-ауты или интервалы, вместо ожидания или истечения времени ожидания, можно ускорить тесты, используя имитацию таймеров, перехватывающую вызовы setTimeout и setInterval.

Смотрите раздел API vi.useFakeTimers api section для более подробного описания API.

Пример ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

function executeAfterTwoHours(func) {
  setTimeout(func, 1000 * 60 * 60 * 2); // 2 hours
}

function executeEveryMinute(func) {
  setInterval(func, 1000 * 60); // 1 minute
}

const mock = vi.fn(() => console.log('executed'));

describe('delayed execution', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });
  afterEach(() => {
    vi.restoreAllMocks();
  });
  it('should execute the function', () => {
    executeAfterTwoHours(mock);
    vi.runAllTimers();
    expect(mock).toHaveBeenCalledTimes(1);
  });
  it('should not execute the function', () => {
    executeAfterTwoHours(mock);
    // advancing by 2ms won't trigger the func
    vi.advanceTimersByTime(2);
    expect(mock).not.toHaveBeenCalled();
  });
  it('should execute every minute', () => {
    executeEveryMinute(mock);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(1);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(2);
  });
});

Шпаргалка ​

INFO

vi в примерах ниже импортируется непосредственно из vitest. Вы также можете использовать его глобально, если установите для globals значение true в вашей конфигурации.

Я хочу…

Шпионить за методом method ​

ts
const instance = new SomeClass();
vi.spyOn(instance, 'method');

Мокировать экспортированные переменные ​

js
// some-path.js
export const getter = 'variable';
ts
// some-path.test.ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked');

Мокировать экспортированную функцию ​

  1. Пример с vi.mock:

WARNING

Не забывайте, что вызов vi.mock поднимается в начало файла. Он всегда будет выполнен перед всеми импортами.

ts
// ./some-path.js
export function method() {}
ts
import { method } from './some-path.js';

vi.mock('./some-path.js', () => ({
  method: vi.fn(),
}));
  1. Пример с vi.spyOn:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'method').mockImplementation(() => {});

Мокировать реализацию экспортированного класса ​

  1. Пример с vi.mock и .prototype:
ts
// some-path.ts
export class SomeClass {}
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn();
  SomeClass.prototype.someMethod = vi.fn();
  return { SomeClass };
});
// SomeClass.mock.instances будет содержать экземпляры SomeClass
  1. Пример с vi.mock и возвращаемым значением:
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn(() => ({
    someMethod: vi.fn(),
  }));
  return { SomeClass };
});
// SomeClass.mock.results будет содержать возвращенные объекты
  1. Пример с vi.spyOn:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
  // whatever suites you from first two examples
});

Шпионить за объектом, возвращаемым из функции ​

  1. Пример с использованием кэша:
ts
// some-path.ts
export function useObject() {
  return { method: () => true };
}
ts
// useObject.js
import { useObject } from './some-path.js';

const obj = useObject();
obj.method();
ts
// useObject.test.js
import { useObject } from './some-path.js';

vi.mock('./some-path.js', () => {
  let _cache;
  const useObject = () => {
    if (!_cache) {
      _cache = {
        method: vi.fn(),
      };
    }
    // теперь каждый раз, когда вызывается useObject(),
    // будет возвращаться одна и та же ссылка на объект
    return _cache;
  };
  return { useObject };
});

const obj = useObject();
// obj.method был вызван внутри some-path
expect(obj.method).toHaveBeenCalled();

Мокировать часть модуля ​

ts
import { mocked, original } from './some-path.js';

vi.mock('./some-path.js', async importOriginal => {
  const mod = await importOriginal<typeof import('./some-path.js')>();
  return {
    ...mod,
    mocked: vi.fn(),
  };
});
original(); // имеет оригинальное поведение
mocked(); // является шпионской функцией

Мокировать текущую дату ​

Чтобы замокать время Date, вы можете использовать функцию vi.setSystemTime. Это значение не будет автоматически сброшено между тестами.

Имейте в виду, что использование vi.useFakeTimers также изменяет время Date.

ts
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// сброс замоканного времени
vi.useRealTimers();

Мокировать глобальную переменную ​

Вы можете установить глобальную переменную, присвоив значение globalThis или используя функцию vi.stubGlobal. При использовании vi.stubGlobal значение не будет автоматически сброшено между тестами, если вы не включите параметр конфигурации unstubGlobals или не вызовете vi.unstubAllGlobals.

ts
vi.stubGlobal('__VERSION__', '1.0.0');
expect(__VERSION__).toBe('1.0.0');

Мокировать import.meta.env ​

  1. Чтобы изменить переменную окружения, вы можете просто присвоить ей новое значение.

WARNING

Значение переменной окружения не будет автоматически сбрасываться между различными тестами.

ts
import { beforeEach, expect, it } from 'vitest';

// вы можете сбросить его вручную в хуке beforeEach
const originalViteEnv = import.meta.env.VITE_ENV;

beforeEach(() => {
  import.meta.env.VITE_ENV = originalViteEnv;
});

it('changes value', () => {
  import.meta.env.VITE_ENV = 'staging';
  expect(import.meta.env.VITE_ENV).toBe('staging');
});
  1. Если вы хотите автоматически сбрасывать значения, вы можете использовать помощник vi.stubEnv с включенной опцией конфигурации unstubEnvs (или вызвать vi.unstubAllEnvs](/api/vi#vi-unstuballenvs) вручную в хуке beforeEach):
ts
import { expect, it, vi } from 'vitest';

// перед запуском тестов "VITE_ENV" имеет значение "test"
import.meta.env.VITE_ENV === 'test';

it('changes value', () => {
  vi.stubEnv('VITE_ENV', 'staging');
  expect(import.meta.env.VITE_ENV).toBe('staging');
});

it('значение восстанавливается перед запуском другого теста', () => {
  expect(import.meta.env.VITE_ENV).toBe('test');
});
ts
// vitest.config.ts
export default {
  test: {
    unstubAllEnvs: true,
  },
};
Pager
Предыдущая страницаСнапшоты
Следующая страницаТестирование типов

Выпущено на условиях лицензии MIT.

Авторские права (c) 2024 Mithril Contributors

https://v1.vitest.dev/guide/mocking

Выпущено на условиях лицензии MIT.

Авторские права (c) 2024 Mithril Contributors