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

快速开始

特性

工作区

命令行界面

测试筛选

报告器

代码覆盖率

快照(Snapshot)

模拟(Mocking)

类型测试

Vitest UI

浏览器模式

源码内测试

测试上下文

测试环境

扩展匹配器

IDE 集成

调试

与其他测试运行器的比较

迁移指南

常见错误

提升性能

API

测试 API 索引

模拟函数

Vi

expect

expectTypeOf

assert(断言)

assertType

配置

管理 Vitest 配置文件

配置 Vitest

页面导航

Vi ​

Vitest 通过其 vi 辅助工具提供实用函数来帮助你。你可以全局访问它(当启用 globals 配置 时),或者直接从 vitest 导入它:

js
import { vi } from 'vitest';

模拟模块 ​

本节介绍在模拟模块时可使用的 API。请注意,Vitest 不支持模拟通过 require() 导入的模块。

vi.mock ​

  • 类型: (path: string, factory?: (importOriginal: () => unknown) => unknown) => void

用另一个模块替换从提供的 path 导入的所有模块。你可以在路径中使用配置的 Vite 别名。对 vi.mock 的调用会被提升(hoisting),因此调用位置不影响执行顺序,它总是在所有导入之前执行。如果你需要在 vi.mock 中引用其作用域之外的变量,可以在 vi.hoisted 中定义这些变量。

WARNING

vi.mock 仅适用于使用 import 关键字导入的模块,不适用于 require。

为了提升 vi.mock,Vitest 会静态分析你的文件。这意味着,不能使用未直接从 vitest 包导入的 vi(例如,从某些实用程序文件导入的 vi)。请使用从 vitest 导入的 vi 调用 vi.mock,或启用 globals 配置选项。

由于在测试文件运行时,setup 文件 中导入的模块已被缓存,Vitest 不会模拟这些模块。你可以在 vi.hoisted 中调用 vi.resetModules() 以在运行测试文件之前清除所有模块缓存。

WARNING

浏览器模式 目前不支持模拟模块。你可以在 GitHub issue 中跟踪此功能。

如果定义了 factory,则所有导入都将返回其结果。Vitest 只调用一次 factory 并缓存结果以供所有后续导入使用,直到调用 vi.unmock 或 vi.doUnmock。

与 Jest 不同,factory 可以是异步的。你可以使用 vi.importActual 或一个辅助函数,并将 factory 作为第一个参数传入,并在内部获取原始模块。

js
import { vi } from 'vitest';
// ---cut---
// when using JavaScript

vi.mock('./path/to/module.js', async importOriginal => {
  const mod = await importOriginal();
  return {
    ...mod,
    // replace some exports
    namedExport: vi.fn(),
  };
});
ts
// when using TypeScript

vi.mock('./path/to/module.js', async importOriginal => {
  const mod = await importOriginal<typeof import('./path/to/module.js')>();
  return {
    ...mod,
    // replace some exports
    namedExport: vi.fn(),
  };
});

WARNING

vi.mock 会被提升到 文件顶部。这意味着无论你在哪里编写它(无论是在 beforeEach 还是 test 中),它实际上都会在该之前被调用。

这也意味着你不能在 factory 中使用任何在其外部定义的变量。

如果需要在 factory 中使用变量,请尝试 vi.doMock。它的工作方式相同,但不会被提升。请注意,它只会模拟后续导入。

如果 vi.hoisted 方法在 vi.mock 之前声明,你也可以引用它定义的变量:

ts
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

如果要模拟具有默认导出的模块,需要在返回的 factory 函数对象中添加一个 default 键。这是一个 ES 模块特有的注意事项;因此,jest 文档可能会有所不同,因为 jest 使用 CommonJS 模块。例如,

ts
vi.mock('./path/to/module.js', () => {
  return {
    default: { myDefaultKey: vi.fn() },
    namedExport: vi.fn(),
    // etc...
  };
});

如果有一个与你要模拟的文件并排的 __mocks__ 文件夹,并且未提供 factory,Vitest 将尝试在 __mocks__ 子文件夹中找到具有相同名称的文件,并将其用作实际模块。如果要模拟依赖项,Vitest 会尝试在项目根目录(root,默认为 process.cwd())下查找 __mocks__ 文件夹。你可以通过 deps.moduleDirectories 配置选项告诉 Vitest 依赖项的位置。

例如,你有以下文件结构:

- __mocks__
  - axios.js
- src
  __mocks__
    - increment.js
  - increment.js
- tests
  - increment.test.js

如果在测试文件中调用 vi.mock 而没有提供 factory,它将找到 __mocks__ 文件夹中的文件以用作模块:

ts
// increment.test.js
import { vi } from 'vitest';

// axios is a default export from `__mocks__/axios.js`
import axios from 'axios';

// increment is a named export from `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 类似的自动模拟效果,可以为 setupFiles 中每个需要模拟的模块调用 vi.mock。

如果没有 __mocks__ 文件夹或提供 factory,Vitest 将导入原始模块并自动模拟其所有导出。有关应用的规则,请参见 算法。

vi.doMock ​

  • 类型: (path: string, factory?: (importOriginal: () => unknown) => unknown) => void

与 vi.mock 相同,但不会被提升到文件顶部,因此你可以在全局文件作用域中引用变量。后续对该模块的动态导入 将会被模拟。

WARNING

此方法不会模拟在此调用之前已经导入的模块。不要忘记 ESM 中的所有静态导入始终是 提升的,因此将此放在静态导入之前并不能保证它在导入之前被调用:

ts
vi.doMock('./increment.js'); // this will be called _after_ the import statement

import { increment } from './increment.js';
ts
// ./increment.js
export function increment(number) {
  return number + 1;
}
ts
import { beforeEach, test } from 'vitest';
import { increment } from './increment.js';

// the module is not mocked, because vi.doMock is not called yet
increment(1) === 2;

let mockedIncrement = 100;

beforeEach(() => {
  // you can access variables inside a factory
  vi.doMock('./increment.js', () => ({ increment: () => ++mockedIncrement }));
});

test('importing the next module imports mocked one', async () => {
  // original import WAS NOT MOCKED, because vi.doMock is evaluated AFTER imports
  expect(increment(1)).toBe(2);
  const { increment: mockedIncrement } = await import('./increment.js');
  // new dynamic import returns mocked module
  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 整个对象都被模拟了(如果实际上是这样)。

ts
import example from './example.js';

vi.mock('./example.js');

test('1 + 1 equals 10', async () => {
  vi.mocked(example.calc).mockReturnValue(10);
  expect(example.calc(1, '+', 1)).toBe(10);
});

vi.importActual ​

  • 类型: <T>(path: string) => Promise<T>

导入模块,绕过所有关于是否应该被模拟的检查。如果你想部分模拟模块,这可能很有用。

ts
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) => void

从模拟注册表中删除模块。即使之前已被模拟,所有对 import 的调用都将返回原始模块。此调用被提升到文件顶部,因此它只会取消模拟在 setupFiles 中定义的模块,例如。

vi.doUnmock ​

  • 类型: (path: string) => void

与 vi.unmock 相同,但不会被提升到文件顶部。后续对该模块的导入将使用原始模块,而不是模拟模块。此方法不会取消模拟之前已经导入的模块。

ts
// ./increment.js
export function increment(number) {
  return number + 1;
}
ts
import { increment } from './increment.js';

// increment is already mocked, because vi.mock is hoisted
increment(1) === 100;

// this is hoisted, and factory is called before the import on line 1
vi.mock('./increment.js', () => ({ increment: () => 100 }));

// all calls are mocked, and `increment` always returns 100
increment(1) === 100;
increment(30) === 100;

// this is not hoisted, so other import will return unmocked module
vi.doUnmock('./increment.js');

// this STILL returns 100, because `vi.doUnmock` doesn't reevaluate a module
increment(1) === 100;
increment(30) === 100;

// the next import is unmocked, now `increment` is the original function that returns count + 1
const { increment: unmockedIncrement } = await import('./increment.js');

unmockedIncrement(1) === 2;
unmockedIncrement(30) === 31;

vi.resetModules ​

  • 类型: () => Vitest

通过清除所有模块的缓存来重置模块注册表。这允许在重新导入时重新评估模块。顶层导入无法被重新评估。在测试之间,如果模块的本地状态发生冲突,可以使用此方法隔离模块。

ts
import { vi } from 'vitest';

import { data } from './data.js'; // Will not get reevaluated beforeEach test

beforeEach(() => {
  vi.resetModules();
});

test('change state', async () => {
  const mod = await import('./some/path.js'); // Will get reevaluated
  mod.changeLocalState('new value');
  expect(mod.getLocalState()).toBe('new value');
});

test('module has old state', async () => {
  const mod = await import('./some/path.js'); // Will get reevaluated
  expect(mod.getLocalState()).toBe('old value');
});

WARNING

不重置模拟注册表。要清除模拟注册表,请使用 vi.unmock 或 vi.doUnmock。

vi.dynamicImportSettled ​

等待所有导入加载完成。如果有一个同步调用触发了模块的导入,且你无法以其他方式等待该模块加载完成,此方法会非常有用。

ts
import { expect, test } from 'vitest';

// cannot track import because Promise is not returned
function renderComponent() {
  import('./component.js').then(({ render }) => {
    render();
  });
}

test('operations are resolved', async () => {
  renderComponent();
  await vi.dynamicImportSettled();
  expect(document.querySelector('.component')).not.toBeNull();
});

TIP

如果在动态导入期间启动了另一个动态导入,则此方法将等待所有这些导入都已解决。

此方法还会等待导入完成后执行下一次 setTimeout,以确保所有同步操作都已完成。

模拟函数和对象 ​

本节介绍如何使用 方法模拟 并替换环境变量和全局变量。

vi.fn ​

  • 类型: (fn?: Function) => Mock

创建一个函数上的侦听器(spy),也可以在不传入函数的情况下创建。每次调用函数时,它都会存储其调用参数、返回值和实例。此外,你可以使用 方法 来操作其行为。 如果没有给出函数,模拟将在调用时返回 undefined。

ts
import { expect, vi } from 'vitest';
// ---cut---
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

创建一个对象的方法或 getter/setter 上的侦听器,其功能类似于 vi.fn()。它返回一个 模拟函数。

ts
import { expect, vi } from 'vitest';
// ---cut---
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

你可以在 afterEach 中调用 vi.restoreAllMocks(或启用 test.restoreMocks)以将所有方法恢复到其原始实现。这将恢复原始的对象描述符,因此你将无法修改方法的实现。

ts
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()); // still 42!

vi.stubEnv 0.26.0+ ​

  • 类型: (name: string, value: string) => Vitest

更改 process.env 和 import.meta.env 中环境变量的值。可通过调用 vi.unstubAllEnvs 恢复原值。

ts
import { vi } from 'vitest';

// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV`
// are "development" before calling "vi.stubEnv"

vi.stubEnv('NODE_ENV', 'production');

process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';
// doesn't change other envs
import.meta.env.MODE === 'development';

TIP

你也可以通过直接赋值来更改环境变量的值,但这样就无法使用 vi.unstubAllEnvs 恢复之前的值。

ts
import.meta.env.MODE = 'test';

vi.unstubAllEnvs 0.26.0+ ​

  • 类型: () => Vitest

恢复所有通过 vi.stubEnv 更改的 import.meta.env 和 process.env 的值。当第一次调用它时,Vitest 会记住原始值并存储它,直到再次调用 unstubAllEnvs。

ts
import { vi } from 'vitest';

// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV`
// are "development" before calling 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();

// restores to the value that were stored before the first "stubEnv" call
process.env.NODE_ENV === 'development';
import.meta.env.NODE_ENV === 'development';

vi.stubGlobal ​

  • 类型: (name: string | number | symbol, value: unknown) => Vitest

更改全局变量的值。你可以通过调用 vi.unstubAllGlobals 来恢复其原始值。

ts
import { vi } from 'vitest';

// `innerWidth` is "0" before calling stubGlobal

vi.stubGlobal('innerWidth', 100);

innerWidth === 100;
globalThis.innerWidth === 100;
// if you are using jsdom or happy-dom
window.innerWidth === 100;

TIP

你也可以通过直接赋值给 globalThis 或 window (如果你正在使用 jsdom 或 happy-dom 环境) 来更改全局变量的值,但这样就无法使用 vi.unstubAllGlobals 恢复原始值。

ts
globalThis.innerWidth = 100;
// if you are using jsdom or happy-dom
window.innerWidth = 100;

vi.unstubAllGlobals 0.26.0+ ​

  • 类型: () => Vitest

恢复 globalThis/global (以及 window/top/self/parent,如果你正在使用 jsdom 或 happy-dom 环境) 中所有通过 vi.stubGlobal 更改的全局变量的值。当第一次调用它时,Vitest 会记住原始值并存储它,直到再次调用 unstubAllGlobals。

ts
import { vi } from 'vitest';

const Mock = vi.fn();

// IntersectionObserver is "undefined" before calling "stubGlobal"

vi.stubGlobal('IntersectionObserver', Mock);

IntersectionObserver === Mock;
global.IntersectionObserver === Mock;
globalThis.IntersectionObserver === Mock;
// if you are using jsdom or happy-dom
window.IntersectionObserver === Mock;

vi.unstubAllGlobals();

globalThis.IntersectionObserver === undefined;
'IntersectionObserver' in globalThis === false;
// throws ReferenceError, because it's not defined
IntersectionObserver === undefined;

伪造定时器 ​

本节描述如何使用模拟定时器。

vi.advanceTimersByTime ​

  • 类型: (ms: number) => Vitest

此方法会推进模拟时钟,执行所有已设置的定时器,直到经过指定的毫秒数或定时器队列为空为止,以先到者为准。

ts
import { vi } from 'vitest';
// ---cut---
let i = 0;
setInterval(() => console.log(++i), 50);

vi.advanceTimersByTime(150);

// log: 1
// log: 2
// log: 3

vi.advanceTimersByTimeAsync ​

  • 类型: (ms: number) => Promise<Vitest>

此方法会异步推进模拟时钟,执行所有已设置的定时器,直到经过指定的毫秒数或定时器队列为空为止,以先到者为准。这将包括异步设置的定时器。

ts
import { vi } from 'vitest';
// ---cut---
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);

await vi.advanceTimersByTimeAsync(150);

// log: 1
// log: 2
// log: 3

vi.advanceTimersToNextTimer ​

  • 类型: () => Vitest

执行下一个待执行的定时器。可以在每次定时器执行之间添加断言,也可以链式调用以手动控制定时器的执行。

ts
import { vi } from 'vitest';
// ---cut---
let i = 0;
setInterval(() => console.log(++i), 50);

vi.advanceTimersToNextTimer() // log: 1
  .advanceTimersToNextTimer() // log: 2
  .advanceTimersToNextTimer(); // log: 3

vi.advanceTimersToNextTimerAsync ​

  • 类型: () => Promise<Vitest>

执行下一个待执行的定时器,并等待它完成(如果它是异步设置的)。可用于在每次定时器执行之间添加断言。

ts
import { expect, vi } from 'vitest';
// ---cut---
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.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 期间调用的每个定时器都将被触发。如果设置了无限循环的定时器,它将在尝试 10000 次后抛出错误(可以使用 fakeTimers.loopLimit 进行配置)。

ts
import { vi } from 'vitest';
// ---cut---
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 期间调用的每个定时器,包括异步定时器,都会被触发。如果设置了无限循环的定时器,它将在尝试 10000 次后抛出错误(可以使用 fakeTimers.loopLimit 进行配置)。

ts
import { vi } from 'vitest';
// ---cut---
setTimeout(async () => {
  console.log(await Promise.resolve('result'));
}, 100);

await vi.runAllTimersAsync();

// log: result

vi.runOnlyPendingTimers ​

  • 类型: () => Vitest

此方法会调用在 vi.useFakeTimers 调用之后启动的每个定时器。它不会触发在其调用期间启动的任何定时器。

ts
import { vi } from 'vitest';
// ---cut---
let i = 0;
setInterval(() => console.log(++i), 50);

vi.runOnlyPendingTimers();

// log: 1

vi.runOnlyPendingTimersAsync ​

  • 类型: () => Promise<Vitest>

此方法会异步调用在 vi.useFakeTimers 调用之后启动的每个定时器,即使是异步定时器。它不会触发在其调用期间启动的任何定时器。

ts
import { vi } from 'vitest';
// ---cut---
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 调用),这将非常有用。

ts
import { expect, vi } from 'vitest';
// ---cut---
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()。

当使用 --pool=forks 在 node:child_process 中运行 Vitest 时,不支持模拟 nextTick。NodeJS 在 node:child_process 中内部使用 process.nextTick,如果对其进行模拟会导致程序挂起。当使用 --pool=threads 运行 Vitest 时,支持模拟 nextTick。

该实现内部基于 @sinonjs/fake-timers。

TIP

从 0.35.0 版本开始,vi.useFakeTimers() 不再自动模拟 process.nextTick。 仍然可以通过在 toFake 参数中指定选项来模拟它:vi.useFakeTimers({ toFake: ['nextTick'] })。

vi.isFakeTimers 0.34.5+ ​

  • 类型: () => boolean

如果启用了模拟定时器,则返回 true。

vi.useRealTimers ​

  • 类型: () => Vitest

当需要恢复到真实定时器时,您可以调用此方法以将模拟定时器恢复到其原始实现。所有之前设置的定时器都将被清除。

其他 ​

Vitest 提供的一组有用的辅助函数。

vi.waitFor 0.34.5+ ​

  • 类型: <T>(callback: WaitForCallback<T>, options?: number | WaitForOptions) => Promise<T>

等待回调函数执行成功。如果回调函数抛出错误或返回被拒绝的 Promise,它将持续等待,直到成功或超时。

当您需要等待某些异步操作完成时,这非常有用,例如,当您启动服务器并需要等待它启动时。

ts
import { expect, test, vi } from 'vitest';
import { createServer } from './server.js';

test('Server started successfully', async () => {
  const server = createServer();

  await vi.waitFor(
    () => {
      if (!server.isReady) throw new Error('Server not started');

      console.log('Server started');
    },
    {
      timeout: 500, // 默认值为 1000
      interval: 20, // 默认值为 50
    }
  );
  expect(server.isReady).toBe(true);
});

它也适用于异步回调

ts
// @vitest-environment jsdom

import { expect, test, vi } from 'vitest';
import { getDOMElementAsync, populateDOMAsync } from './dom.js';

test('Element exists in a DOM', async () => {
  // start populating 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 0.34.5+ ​

  • 类型: <T>(callback: WaitUntilCallback<T>, options?: number | WaitUntilOptions) => Promise<T>

这与 vi.waitFor 类似,但如果回调函数抛出任何错误,执行会立即中断并抛出错误。如果回调函数返回假值(falsy),则下一次检查将继续,直到返回真值(truthy)。当您需要在执行下一步之前等待某些内容存在时,这非常有用。

参考以下示例。我们可以使用 vi.waitUntil 等待元素出现在页面上,然后我们可以对该元素进行后续操作。

ts
import { expect, test, vi } from 'vitest';

test('Element render correctly', async () => {
  const element = await vi.waitUntil(() => document.querySelector('.element'), {
    timeout: 500, // 默认值为 1000
    interval: 20, // 默认值为 50
  });

  // do something with the element
  expect(element.querySelector('.element-child')).toBeTruthy();
});

vi.hoisted 0.31.0+ ​

  • 类型: <T>(factory: () => T) => T

ES 模块中的所有静态 import 语句都会被提升到文件的顶部,因此在导入语句之前定义的任何代码实际上会在导入语句执行后执行。

但是,在导入模块之前执行一些有副作用的操作(例如模拟日期)可能很有用。

为了绕过这个限制,您可以将静态导入重写为动态导入,如下所示:

diff
callFunctionWithSideEffect()
- import { value } from './some/module.js'
+ const { value } = await import('./some/module.js')

当运行 vitest 时,您可以使用 vi.hoisted 方法自动执行此操作。

diff
- callFunctionWithSideEffect()
import { value } from './some/module.js'
+ vi.hoisted(() => callFunctionWithSideEffect())

此方法返回从工厂函数返回的值。如需方便地访问局部变量,则可以在 vi.mock 工厂函数中使用该值:

ts
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);

注意:即使运行环境不支持顶层 await,也可以异步调用此方法:

ts
const promised = await vi.hoisted(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
});

vi.setConfig ​

  • 类型: RuntimeConfig

更新当前测试文件的配置。此方法仅支持影响当前测试文件的配置选项:

ts
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,此方法会将配置重置为原始状态。

Pager
上一页模拟函数
下一页expect

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors

https://v1.vitest.dev/api/vi

基于 MIT 许可证 发布。

版权所有 (c) 2024 Mithril Contributors