Vi
Vitest предоставляет утилиты через свой помощник vi
. Вы можете получить к нему доступ глобально (при включенной глобальной конфигурации) или импортировать его напрямую из vitest
:
import { vi } from 'vitest';
Мокинг модулей
В этом разделе описывается API, который можно использовать для мокирования модуля. Имейте в виду, что Vitest не поддерживает мокирование модулей, импортированных через require()
.
vi.mock
- Тип:
(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
- Тип:
<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void
Заменяет все модули, импортированные из указанного path
, на другой модуль. Вы можете использовать настроенные Vite алиасы внутри пути. Вызов vi.mock
всплывает (hoisted), поэтому не имеет значения, где вы его вызываете. Он всегда будет выполнен перед всеми импортами. Если вам нужно ссылаться на переменные вне его области видимости, вы можете определить их внутри vi.hoisted
и использовать внутри vi.mock
.
WARNING
vi.mock
работает только с модулями, импортированными с помощью ключевого слова import
. Он не работает с require
.
Чтобы поднять vi.mock
, Vitest выполняет статический анализ ваших файлов. Это означает, что vi
, не импортированный напрямую из пакета vitest
(например, из некоторого вспомогательного файла), не может быть использован. Используйте vi.mock
с vi
, импортированным из vitest
, или включите опцию конфигурации globals
.
Vitest не будет мокировать модули, которые были импортированы внутри файла настройки, потому что они кэшируются к моменту, когда тестовый файл запускается. Вы можете вызвать vi.resetModules()
внутри vi.hoisted
, чтобы очистить все кэши модулей перед запуском тестового файла.
Если функция-фабрика factory
определена, все импорты будут возвращать ее результат. Vitest вызывает фабрику только один раз и кэширует результаты для всех последующих операций импорта до вызова vi.unmock
или vi.doUnmock
.
В отличие от jest
, фабрика может быть асинхронной. Вы можете использовать vi.importActual
или вспомогательную функцию, которой передается фабрика в качестве первого аргумента, и получить оригинальный модуль внутри.
Начиная с Vitest 2.1, вы также можете предоставить объект со свойством spy
вместо функции-фабрики. Если spy
равно true
, то Vitest будет автоматически мокировать модуль как обычно, но не будет переопределять реализацию экспортируемых сущностей. Это полезно, если вы просто хотите убедиться, что экспортированный метод был правильно вызван другим методом.
import { calculator } from './src/calculator.ts';
vi.mock('./src/calculator.ts', { spy: true });
// вызывает оригинальную реализацию,
// но позволяет позже проверить поведение
const result = calculator(1, 2);
expect(result).toBe(3);
expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);
Vitest также поддерживает промис, разрешающийся в модуль, вместо строки в методах vi.mock
и vi.doMock
для лучшей поддержки IDE. При перемещении файла путь будет обновлен, а importOriginal
автоматически выводит тип. Использование этой сигнатуры также обеспечит совместимость возвращаемого типа функции-фабрики с оригинальным модулем (сохраняя экспорты необязательными).
// @filename: ./path/to/module.js
export declare function total(...numbers: number[]): number;
// @filename: test.js
import { vi } from 'vitest';
// ---cut---
vi.mock(import('./path/to/module.js'), async importOriginal => {
const mod = await importOriginal(); // тип выводится
// ^?
return {
...mod,
// заменить некоторые экспорты
total: vi.fn(),
};
});
Внутри Vitest по-прежнему работает со строкой, а не с объектом модуля.
Однако, если вы используете TypeScript с алиасами paths
, настроенными в tsconfig.json
, компилятор не сможет правильно разрешить типы импорта. Чтобы это работало, убедитесь, что вы заменили все импорты с алиасами на соответствующие относительные пути. Например, используйте import('./path/to/module.js')
вместо import('@/module')
.
WARNING
vi.mock
всплывает (другими словами, перемещается) в верхнюю часть файла. Это означает, что независимо от того, где вы его напишете (будь то внутри beforeEach
или test
), он фактически будет вызван до выполнения кода в этих блоках.
Это также означает, что вы не можете использовать какие-либо переменные внутри функции-фабрики, которые определены вне функции-фабрики.
Если вам нужно использовать переменные внутри функции-фабрики, попробуйте vi.doMock
. Он работает так же, но не подвергается поднятию. Обратите внимание, что он мокирует только последующие импорты.
Вы также можете ссылаться на переменные, определенные с помощью vi.hoisted
, если он был объявлен до vi.mock
:
import { namedExport } from './path/to/module.js';
const mocks = vi.hoisted(() => {
return {
namedExport: vi.fn(),
};
});
vi.mock('./path/to/module.js', () => {
return {
namedExport: mocks.namedExport,
};
});
vi.mocked(namedExport).mockReturnValue(100);
expect(namedExport()).toBe(100);
expect(namedExport).toBe(mocks.namedExport);
WARNING
Если вы мокируете модуль с экспортом по умолчанию, вам нужно будет предоставить ключ default
внутри объекта функции-фабрики. Это особенность, характерная для модулей ES; поэтому документация jest
может отличаться, поскольку jest
использует модули CommonJS. Например,
vi.mock('./path/to/module.js', () => {
return {
default: { myDefaultKey: vi.fn() },
namedExport: vi.fn(),
// и так далее
};
});
Если рядом с файлом, который вы мокируете, есть папка __mocks__
, и функция-фабрика не предоставлена, Vitest попытается найти файл с тем же именем в подпапке __mocks__
и использовать его как фактический модуль. Если вы мокируете зависимость, Vitest попытается найти папку __mocks__
в корневом каталоге проекта (по умолчанию process.cwd()
). Вы можете указать Vitest, где находятся зависимости, используя опцию конфигурации deps.moduleDirectories
.
Например, у вас такая структура файлов:
- __mocks__
- axios.js
- src
__mocks__
- increment.js
- increment.js
- tests
- increment.test.js
Если вы вызываете vi.mock
в тестовом файле без предоставления factory или опций, он найдет файл в папке __mocks__
для использования в качестве модуля:
// increment.test.js
import { vi } from 'vitest';
// axios - это экспорт по умолчанию (default export) из `__mocks__/axios.js`
import axios from 'axios';
// increment - это именованный экспорт (named export) из `src/__mocks__/increment.js`
import { increment } from '../increment.js';
vi.mock('axios');
vi.mock('../increment.js');
axios.get(`/apples/${increment(1)}`);
WARNING
Обратите внимание, что если вы не вызываете vi.mock
, модули не подвергаются мокированию автоматически. Чтобы воспроизвести поведение Jest по автоматическому мокированию, вы можете вызвать vi.mock
для каждого требуемого модуля внутри setupFiles
.
Если папка __mocks__
отсутствует или factory не предоставлен, Vitest импортирует оригинальный модуль и автоматически мокирует все его экспорты. Правила, которые при этом применяются, см. в разделе алгоритм.
vi.doMock
- Тип:
(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
- Тип:
<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void
То же, что и vi.mock
, но не подвергается поднятию в верхнюю часть файла, поэтому вы можете ссылаться на переменные в глобальной области видимости файла. Следующий динамический импорт модуля будет подвергнут мокированию.
WARNING
Это не будет подвергать мокированию модули, которые были импортированы до вызова этого метода. Не забывайте, что все статические импорты в ESM всегда подвергаются поднятию, поэтому размещение этого метода перед статическим импортом не приведет к его вызову до импорта:
vi.doMock('./increment.js'); // это будет вызвано _после_ оператора import
import { increment } from './increment.js';
// ./increment.js
export function increment(number) {
return number + 1;
}
import { beforeEach, test } from 'vitest';
import { increment } from './increment.js';
// модуль не подвергнут мокированию, потому что vi.doMock еще не вызван
increment(1) === 2;
let mockedIncrement = 100;
beforeEach(() => {
// вы можете получить доступ к переменным внутри функции-фабрики
vi.doMock('./increment.js', () => ({ increment: () => ++mockedIncrement }));
});
test('импорт следующего модуля импортирует замоканный', async () => {
// оригинальный импорт НЕ БЫЛ ПОДВЕРГНУТ МОКИРОВАНИЮ, потому что vi.doMock оценивается ПОСЛЕ импортов
expect(increment(1)).toBe(2);
const { increment: mockedIncrement } = await import('./increment.js');
// новый динамический импорт возвращает модуль, подвергнутый мокированию
expect(mockedIncrement(1)).toBe(101);
expect(mockedIncrement(1)).toBe(102);
expect(mockedIncrement(1)).toBe(103);
});
vi.mocked
- Тип:
<T>(obj: T, deep?: boolean) => MaybeMockedDeep<T>
- Тип:
<T>(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep<T>
Вспомогательная функция для типизации TypeScript. Просто возвращает переданный объект.
Когда partial
равно true
, он будет ожидать Partial<T>
в качестве возвращаемого значения. По умолчанию это заставит TypeScript считать, что подвергнуты мокированию только значения первого уровня. Вы можете передать { deep: true }
в качестве второго аргумента, чтобы сообщить TypeScript, что весь объект подвергнут мокированию, если это действительно так.
// example.ts
export function add(x: number, y: number): number {
return x + y;
}
export function fetchSomething(): Promise<Response> {
return fetch('https://vitest.dev/');
}
// example.test.ts
import * as example from './example';
vi.mock('./example');
test('1 + 1 равно 10', async () => {
vi.mocked(example.add).mockReturnValue(10);
expect(example.add(1, 1)).toBe(10);
});
test('мокирование возвращаемого значения с частично корректной типизацией', async () => {
vi.mocked(example.fetchSomething).mockResolvedValue(new Response('hello'));
vi.mocked(example.fetchSomething, { partial: true }).mockResolvedValue({
ok: false,
});
// vi.mocked(example.someFn).mockResolvedValue({ ok: false }) // это приведет к ошибке типа
});
vi.importActual
- Тип:
<T>(path: string) => Promise<T>
Импортирует модуль, обходя все проверки, нужно ли его мокировать. Может быть полезно, если вы хотите частично мокировать модуль.
vi.mock('./example.js', async () => {
const axios = await vi.importActual('./example.js');
return { ...axios, get: vi.fn() };
});
vi.importMock
- Тип:
<T>(path: string) => Promise<MaybeMockedDeep<T>>
Импортирует модуль со всеми его свойствами (включая вложенные свойства), подвергнутыми мокированию. Следует тем же правилам, что и vi.mock
. Правила, которые при этом применяются, см. в разделе алгоритм.
vi.unmock
- Тип:
(path: string | Promise<Module>) => void
Удаляет модуль из реестра мокированных модулей. Все вызовы импорта будут возвращать оригинальный модуль, даже если он был подвергнут мокированию ранее. Этот вызов подвергается поднятию в верхнюю часть файла, поэтому он будет отменять мокирование только модулей, которые были определены, например, в setupFiles
.
vi.doUnmock
- Тип:
(path: string | Promise<Module>) => void
То же, что и vi.unmock
, но не подвергается поднятию в верхнюю часть файла. Следующий импорт модуля будет импортировать оригинальный модуль вместо мока. Это не будет отменять мокирование ранее импортированные модули.
// ./increment.js
export function increment(number) {
return number + 1;
}
import { increment } from './increment.js';
// increment уже подвергнут мокированию, потому что vi.mock подвергнут поднятию
increment(1) === 100;
// это подвергнуто поднятию, и функция-фабрика вызывается перед импортом на строке 1
vi.mock('./increment.js', () => ({ increment: () => 100 }));
// все вызовы подвергнуты мокированию, и `increment` всегда возвращает 100
increment(1) === 100;
increment(30) === 100;
// это не подвергнуто поднятию, поэтому другой импорт вернет немокированный модуль
vi.doUnmock('./increment.js');
// это ВСЕ ЕЩЕ возвращает 100, потому что `vi.doUnmock` не приводит к повторной оценке модуля
increment(1) === 100;
increment(30) === 100;
// следующий импорт не подвергнут мокированию, теперь `increment` - это оригинальная функция, которая возвращает count + 1
const { increment: unmockedIncrement } = await import('./increment.js');
unmockedIncrement(1) === 2;
unmockedIncrement(30) === 31;
vi.resetModules
- Тип:
() => Vitest
Сбрасывает реестр загруженных модулей, очищая кэш всех модулей. Это позволяет выполнять повторную оценку модулей при повторном импорте. Импорты верхнего уровня не могут быть повторно оценены. Может быть полезно для изоляции модулей, в которых локальное состояние конфликтует между тестами.
import { vi } from 'vitest';
import { data } from './data.js'; // Не будет подвергаться повторной оценке перед каждым тестом
beforeEach(() => {
vi.resetModules();
});
test('изменить состояние', async () => {
const mod = await import('./some/path.js'); // Будет подвергаться повторной оценке
mod.changeLocalState('новое значение');
expect(mod.getLocalState()).toBe('новое значение');
});
test('модуль имеет старое состояние', async () => {
const mod = await import('./some/path.js'); // Будет подвергаться повторной оценке
expect(mod.getLocalState()).toBe('старое значение');
});
WARNING
Не сбрасывает реестр мокированных модулей. Чтобы очистить реестр моков, используйте vi.unmock
или vi.doUnmock
.
vi.dynamicImportSettled
Ожидает загрузки всех импортов. Полезно, если у вас есть синхронный вызов, который начинает импортировать модуль, завершения которого вы иначе не можете дождаться.
import { expect, test } from 'vitest';
// невозможно отследить импорт, потому что Promise не возвращается
function renderComponent() {
import('./component.js').then(({ render }) => {
render();
});
}
test('операции завершены', async () => {
renderComponent();
await vi.dynamicImportSettled();
expect(document.querySelector('.component')).not.toBeNull();
});
TIP
Если во время динамического импорта инициируется другой динамический импорт, этот метод будет ждать, пока все они не завершатся.
Этот метод также будет ждать следующего тика setTimeout
после завершения импорта, чтобы все синхронные операции были завершены к моменту его разрешения.
Мокирование функций и объектов
В этом разделе описывается, как работать с мокированием методов и заменять переменные окружения и глобальные переменные.
vi.fn
- Тип:
(fn?: Function) => Mock
Создает шпиона для функции, хотя может быть инициализирован без нее. Каждый раз, когда функция вызывается, она сохраняет свои аргументы, с которыми она была вызвана, возвращенные значения и экземпляры. Также вы можете управлять ее поведением с помощью методов. Если функция не задана, мок-функция будет возвращать undefined
при вызове.
const getApples = vi.fn(() => 0);
getApples();
expect(getApples).toHaveBeenCalled();
expect(getApples).toHaveReturnedWith(0);
getApples.mockReturnValueOnce(5);
const res = getApples();
expect(res).toBe(5);
expect(getApples).toHaveNthReturnedWith(2, 5);
vi.isMockFunction
- Тип:
(fn: Function) => boolean
Проверяет, является ли данный параметр мок-функцией. Если вы используете TypeScript, он также уточнит ее тип.
vi.clearAllMocks
Вызовет .mockClear()
для всех шпионов. Это очистит историю вызовов моков, но не сбросит их реализацию до значения по умолчанию.
vi.resetAllMocks
Вызовет .mockReset()
для всех шпионов. Это очистит историю вызовов моков и сбросит их реализацию до пустой функции (будет возвращать undefined
).
vi.restoreAllMocks
Вызовет .mockRestore()
для всех шпионов. Это очистит историю вызовов моков и сбросит их реализацию до оригинальной.
vi.spyOn
- Тип:
<T, K extends keyof T>(object: T, method: K, accessType?: 'get' | 'set') => MockInstance
Создает шпиона для метода или геттера/сеттера объекта, аналогично vi.fn()
. Он возвращает мок-функцию.
let apples = 0;
const cart = {
getApples: () => 42,
};
const spy = vi.spyOn(cart, 'getApples').mockImplementation(() => apples);
apples = 1;
expect(cart.getApples()).toBe(1);
expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(1);
TIP
Вы можете вызвать vi.restoreAllMocks
внутри afterEach
(или включить test.restoreMocks
), чтобы восстановить все методы до их оригинальных реализаций. Это восстановит оригинальный дескриптор объекта, поэтому вы не сможете изменить реализацию метода:
const cart = {
getApples: () => 42,
};
const spy = vi.spyOn(cart, 'getApples').mockReturnValue(10);
console.log(cart.getApples()); // 10
vi.restoreAllMocks();
console.log(cart.getApples()); // 42
spy.mockReturnValue(10);
console.log(cart.getApples()); // все еще 42!
TIP
Невозможно создавать шпионов для экспортированных методов в режиме браузера. Вместо этого вы можете создавать шпионов для каждого экспортированного метода, вызвав vi.mock("./file-path.js", { spy: true })
. Это будет подвергать мокированию каждый экспорт, но сохранит его реализацию нетронутой, позволяя вам проверить, был ли метод вызван правильно.
import { calculator } from './src/calculator.ts';
vi.mock('./src/calculator.ts', { spy: true });
calculator(1, 2);
expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);
И хотя возможно создавать шпионов для экспортов в jsdom
или других средах Node.js, это может измениться в будущем.
vi.stubEnv
- Тип:
<T extends string>(name: T, value: T extends "PROD" | "DEV" | "SSR" ? boolean : string | undefined) => Vitest
Изменяет значение переменной окружения в process.env
и import.meta.env
. Вы можете восстановить ее значение, вызвав vi.unstubAllEnvs
.
import { vi } from 'vitest';
// `process.env.NODE_ENV` и `import.meta.env.NODE_ENV`
// равны "development" до вызова "vi.stubEnv"
vi.stubEnv('NODE_ENV', 'production');
process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';
vi.stubEnv('NODE_ENV', undefined);
process.env.NODE_ENV === undefined;
import.meta.env.NODE_ENV === undefined;
// не изменяет другие переменные окружения
import.meta.env.MODE === 'development';
TIP
Вы также можете изменить значение, просто присвоив его, но вы не сможете использовать vi.unstubAllEnvs
для восстановления предыдущего значения:
import.meta.env.MODE = 'test';
vi.unstubAllEnvs
- Тип:
() => Vitest
Восстанавливает все значения import.meta.env
и process.env
, которые были изменены с помощью vi.stubEnv
. При первом вызове Vitest запоминает оригинальное значение и будет хранить его до следующего вызова unstubAllEnvs
.
import { vi } from 'vitest';
// `process.env.NODE_ENV` и `import.meta.env.NODE_ENV`
// равны "development" до вызова stubEnv
vi.stubEnv('NODE_ENV', 'production');
process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';
vi.stubEnv('NODE_ENV', 'staging');
process.env.NODE_ENV === 'staging';
import.meta.env.NODE_ENV === 'staging';
vi.unstubAllEnvs();
// восстанавливает значение, которое было сохранено до первого вызова "stubEnv"
process.env.NODE_ENV === 'development';
import.meta.env.NODE_ENV === 'development';
vi.stubGlobal
- Тип:
(name: string | number | symbol, value: unknown) => Vitest
Изменяет значение глобальной переменной. Вы можете восстановить ее оригинальное значение, вызвав vi.unstubAllGlobals
.
import { vi } from 'vitest';
// `innerWidth` равно "0" до вызова stubGlobal
vi.stubGlobal('innerWidth', 100);
innerWidth === 100;
globalThis.innerWidth === 100;
// если вы используете jsdom или happy-dom
window.innerWidth === 100;
TIP
Вы также можете изменить значение, просто присвоив его globalThis
или window
(если вы используете среду jsdom
или happy-dom
), но вы не сможете использовать vi.unstubAllGlobals
для восстановления оригинального значения:
globalThis.innerWidth = 100;
// если вы используете jsdom или happy-dom
window.innerWidth = 100;
vi.unstubAllGlobals
- Тип:
() => Vitest
Восстанавливает все глобальные значения в globalThis
/global
(и window
/top
/self
/parent
, если вы используете среду jsdom
или happy-dom
), которые были изменены с помощью vi.stubGlobal
. При первом вызове Vitest запоминает оригинальное значение и будет хранить его до следующего вызова unstubAllGlobals
.
import { vi } from 'vitest';
const Mock = vi.fn();
// IntersectionObserver равно "undefined" до вызова "stubGlobal"
vi.stubGlobal('IntersectionObserver', Mock);
IntersectionObserver === Mock;
global.IntersectionObserver === Mock;
globalThis.IntersectionObserver === Mock;
// если вы используете jsdom или happy-dom
window.IntersectionObserver === Mock;
vi.unstubAllGlobals();
globalThis.IntersectionObserver === undefined;
'IntersectionObserver' in globalThis === false;
// выбрасывает ReferenceError, потому что не определено
IntersectionObserver === undefined;
Поддельные таймеры
В этом разделе описывается, как работать с поддельными таймерами.
vi.advanceTimersByTime
- Тип:
(ms: number) => Vitest
Этот метод вызовет каждый инициированный таймер до тех пор, пока не пройдет указанное количество миллисекунд или очередь не станет пустой - что произойдет раньше.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.advanceTimersByTime(150);
// log: 1
// log: 2
// log: 3
vi.advanceTimersByTimeAsync
- Тип:
(ms: number) => Promise<Vitest>
Этот метод вызовет каждый инициированный таймер до тех пор, пока не пройдет указанное количество миллисекунд или очередь не станет пустой - что произойдет раньше. Это будет включать асинхронно установленные таймеры.
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersByTimeAsync(150);
// log: 1
// log: 2
// log: 3
vi.advanceTimersToNextTimer
- Тип:
() => Vitest
Вызовет следующий доступный таймер. Полезно для выполнения проверок между каждым вызовом таймера. Вы можете вызывать его цепочкой, чтобы управлять таймерами самостоятельно.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.advanceTimersToNextTimer() // log: 1
.advanceTimersToNextTimer() // log: 2
.advanceTimersToNextTimer(); // log: 3
vi.advanceTimersToNextTimerAsync
- Тип:
() => Promise<Vitest>
Вызовет следующий доступный таймер и будет ждать его завершения, если он был установлен асинхронно. Полезно для выполнения проверок между каждым вызовом таймера.
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersToNextTimerAsync(); // log: 1
expect(console.log).toHaveBeenCalledWith(1);
await vi.advanceTimersToNextTimerAsync(); // log: 2
await vi.advanceTimersToNextTimerAsync(); // log: 3
vi.advanceTimersToNextFrame 2.1.0+
- Тип:
() => Vitest
Аналогично vi.advanceTimersByTime
, но будет продвигать таймеры на количество миллисекунд, необходимое для выполнения колбэков, запланированных в данный момент с помощью requestAnimationFrame
.
let frameRendered = false;
requestAnimationFrame(() => {
frameRendered = true;
});
vi.advanceTimersToNextFrame();
expect(frameRendered).toBe(true);
vi.getTimerCount
- Тип:
() => number
Получить количество ожидающих таймеров.
vi.clearAllTimers
Удаляет все таймеры, запланированные к выполнению. Эти таймеры никогда не будут выполнены в будущем.
vi.getMockedSystemTime
- Тип:
() => Date | null
Возвращает поддельную текущую дату, которая была установлена с помощью setSystemTime
. Если дата не подделана, метод вернет null
.
vi.getRealSystemTime
- Тип:
() => number
При использовании vi.useFakeTimers
вызовы Date.now
подделываются. Если вам нужно получить реальное время в миллисекундах, вы можете вызвать эту функцию.
vi.runAllTicks
- Тип:
() => Vitest
Вызывает каждую микрозадачу, которая была поставлена в очередь с помощью process.nextTick
. Это также запустит все микрозадачи, запланированные самими собой.
vi.runAllTimers
- Тип:
() => Vitest
Этот метод вызовет каждый инициированный таймер до тех пор, пока очередь таймеров не станет пустой. Это означает, что каждый таймер, вызванный во время runAllTimers
, будет запущен. Если у вас бесконечный интервал, он выбросит исключение после 10 000 попыток (можно настроить с помощью fakeTimers.loopLimit
).
let i = 0;
setTimeout(() => console.log(++i));
const interval = setInterval(() => {
console.log(++i);
if (i === 3) {
clearInterval(interval);
}
}, 50);
vi.runAllTimers();
// log: 1
// log: 2
// log: 3
vi.runAllTimersAsync
- Тип:
() => Promise<Vitest>
Этот метод асинхронно вызовет каждый инициированный таймер до тех пор, пока очередь таймеров не станет пустой. Это означает, что каждый таймер, вызванный во время runAllTimersAsync
, будет запущен, даже асинхронные таймеры. Если у вас бесконечный интервал, он выбросит исключение после 10 000 попыток (можно настроить с помощью fakeTimers.loopLimit
).
setTimeout(async () => {
console.log(await Promise.resolve('result'));
}, 100);
await vi.runAllTimersAsync();
// log: result
vi.runOnlyPendingTimers
- Тип:
() => Vitest
Этот метод вызовет каждый таймер, который был инициирован после вызова vi.useFakeTimers
. Он не будет запускать таймеры, которые были инициированы во время его вызова.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.runOnlyPendingTimers();
// log: 1
vi.runOnlyPendingTimersAsync
- Тип:
() => Promise<Vitest>
Этот метод асинхронно вызовет каждый таймер, который был инициирован после вызова vi.useFakeTimers
, даже асинхронные. Он не будет запускать таймеры, которые были инициированы во время его вызова.
setTimeout(() => {
console.log(1);
}, 100);
setTimeout(() => {
Promise.resolve().then(() => {
console.log(2);
setInterval(() => {
console.log(3);
}, 40);
});
}, 10);
await vi.runOnlyPendingTimersAsync();
// log: 2
// log: 3
// log: 3
// log: 1
vi.setSystemTime
- Тип:
(date: string | number | Date) => void
Если поддельные таймеры включены, этот метод имитирует изменение системных часов пользователем (повлияет на API, связанные с датой, такие как hrtime
, performance.now
или new Date()
) - однако он не будет запускать никакие таймеры. Если поддельные таймеры не включены, этот метод будет подделывать только вызовы Date.*
.
Полезно, если вам нужно протестировать что-либо, зависящее от текущей даты - например, вызовы Luxon в вашем коде.
Принимает те же строковые и числовые аргументы, что и Date
.
const date = new Date(1998, 11, 19);
vi.useFakeTimers();
vi.setSystemTime(date);
expect(Date.now()).toBe(date.valueOf());
vi.useRealTimers();
vi.useFakeTimers
- Тип:
(config?: FakeTimerInstallOpts) => Vitest
Чтобы включить имитацию таймеров, вам нужно вызвать этот метод. Он будет перехватывать все последующие вызовы таймеров (таких как setTimeout
, setInterval
, clearTimeout
, clearInterval
, setImmediate
, clearImmediate
и Date
) до вызова vi.useRealTimers()
.
Имитация nextTick
не поддерживается при запуске Vitest внутри node:child_process
с использованием --pool=forks
. NodeJS использует process.nextTick
внутри node:child_process
и зависает при его имитации. Имитация nextTick
поддерживается при запуске Vitest с --pool=threads
.
Реализация основана на @sinonjs/fake-timers
.
TIP
vi.useFakeTimers()
не имитирует process.nextTick
автоматически. Но вы можете включить его, указав опцию в аргументе toFake
: vi.useFakeTimers({ toFake: ['nextTick'] })
.
vi.isFakeTimers
- Тип:
() => boolean
Возвращает true
, если поддельные таймеры включены.
vi.useRealTimers
- Тип:
() => Vitest
Когда поддельные таймеры больше не нужны, вы можете вызвать этот метод, чтобы вернуть имитированные таймеры к их оригинальным реализациям. Все таймеры, которые были запланированы ранее, будут отменены.
Разное
Набор полезных вспомогательных функций, предоставляемых Vitest.
vi.waitFor
- Тип:
<T>(callback: WaitForCallback<T>, options?: number | WaitForOptions) => Promise<T>
Ожидает успешного выполнения колбэка. Если колбэк выбрасывает ошибку или возвращает отклоненный промис, он будет продолжать ждать, пока не выполнится успешно или не истечет время ожидания.
Это очень полезно, когда вам нужно дождаться завершения какого-либо асинхронного действия, например, когда вы запускаете сервер и нужно дождаться его запуска.
import { expect, test, vi } from 'vitest';
import { createServer } from './server.js';
test('Сервер успешно стартовал', async () => {
const server = createServer();
await vi.waitFor(
() => {
if (!server.isReady) {
throw new Error('Сервер не запущен');
}
console.log('Сервер запущен');
},
{
timeout: 500, // по умолчанию 1000
interval: 20, // по умолчанию 50
}
);
expect(server.isReady).toBe(true);
});
Это также работает для асинхронных колбэков
// @vitest-environment jsdom
import { expect, test, vi } from 'vitest';
import { getDOMElementAsync, populateDOMAsync } from './dom.js';
test('Элемент существует в DOM', async () => {
// начать заполнение DOM
populateDOMAsync();
const element = await vi.waitFor(
async () => {
// пытаться получить элемент, пока он не появится
const element = (await getDOMElementAsync()) as HTMLElement | null;
expect(element).toBeTruthy();
expect(element.dataset.initialized).toBeTruthy();
return element;
},
{
timeout: 500, // по умолчанию 1000
interval: 20, // по умолчанию 50
}
);
expect(element).toBeInstanceOf(HTMLElement);
});
Если используется vi.useFakeTimers
, vi.waitFor
автоматически вызывает vi.advanceTimersByTime(interval)
при каждом колбэке проверки.
vi.waitUntil
- Тип:
<T>(callback: WaitUntilCallback<T>, options?: number | WaitUntilOptions) => Promise<T>
Это похоже на vi.waitFor
, но если колбэк выбрасывает какие-либо ошибки, выполнение немедленно прерывается и выдается сообщение об ошибке. Если колбэк возвращает ложное значение, следующая проверка будет продолжаться до тех пор, пока не будет возвращено истинное значение. Это полезно, когда вам нужно дождаться появления чего-либо, прежде чем предпринимать следующий шаг.
Посмотрите на пример ниже. Мы можем использовать vi.waitUntil
, чтобы дождаться появления элемента на странице, а затем мы можем что-то сделать с этим элементом.
import { expect, test, vi } from 'vitest';
test('Элемент отображается правильно', async () => {
const element = await vi.waitUntil(() => document.querySelector('.element'), {
timeout: 500, // по умолчанию 1000
interval: 20, // по умолчанию 50
});
// что-то сделать с элементом
expect(element.querySelector('.element-child')).toBeTruthy();
});
vi.hoisted
- Тип:
<T>(factory: () => T) => T
Все статические операторы import
в модулях ES подвергаются поднятию в верхнюю часть файла, поэтому любой код, определенный до импортов, фактически будет выполнен после оценки модулей, которые импортируются.
Однако может быть полезно вызвать некоторые побочные эффекты, такие как имитация дат, перед импортом модуля.
Чтобы обойти это ограничение, вы можете переписать статические импорты в динамические следующим образом:
callFunctionWithSideEffect()
- import { value } from './some/module.js'
+ const { value } = await import('./some/module.js')
При запуске vitest
вы можете сделать это автоматически, используя метод vi.hoisted
.
- callFunctionWithSideEffect()
import { value } from './some/module.js'
+ vi.hoisted(() => callFunctionWithSideEffect())
Этот метод возвращает значение, которое было возвращено из функции-фабрики. Вы можете использовать это значение в функциях-фабриках для vi.mock
, если вам нужен легкий доступ к локально определенным переменным:
import { expect, vi } from 'vitest';
import { originalMethod } from './path/to/module.js';
const { mockedMethod } = vi.hoisted(() => {
return { mockedMethod: vi.fn() };
});
vi.mock('./path/to/module.js', () => {
return { originalMethod: mockedMethod };
});
mockedMethod.mockReturnValue(100);
expect(originalMethod()).toBe(100);
Обратите внимание, что этот метод также может быть вызван асинхронно, даже если ваша среда не поддерживает top-level await:
const promised = await vi.hoisted(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
return response.json();
});
vi.setConfig
- Тип:
RuntimeConfig
Обновляет конфигурацию для текущего тестового файла. Этот метод поддерживает только опции конфигурации, которые применяются к текущему тестовому файлу:
vi.setConfig({
allowOnly: true,
testTimeout: 10_000,
hookTimeout: 10_000,
clearMocks: true,
restoreMocks: true,
fakeTimers: {
now: new Date(2021, 11, 19),
// поддерживает весь объект
},
maxConcurrency: 10,
sequence: {
hooks: 'stack',
// поддерживает только "sequence.hooks"
},
});
vi.resetConfig
- Тип:
RuntimeConfig
Если ранее был вызван vi.setConfig
, это сбросит конфигурацию до исходного состояния.