模拟函数
你可以使用 vi.fn
方法创建一个模拟函数(Mock Function)来跟踪其调用。如果你想跟踪一个已创建对象的方法,可以使用 vi.spyOn
方法:
import { vi } from 'vitest';
const fn = vi.fn();
fn('hello world');
fn.mock.calls[0] === ['hello world'];
const market = {
getApples: () => 100,
};
const getApplesSpy = vi.spyOn(market, 'getApples');
market.getApples();
getApplesSpy.mock.calls.length === 1;
你应该使用 expect
上的模拟断言(例如 toHaveBeenCalled
)来断言模拟结果。本 API 参考文档描述了可用的属性和方法来控制模拟行为。
TIP
下面类型定义中自定义函数实现使用泛型 <T>
标注。
getMockImplementation
function getMockImplementation(): T | undefined;
如果存在,则返回当前的模拟实现函数。
如果 mock 是使用 vi.fn
创建的,它将使用提供的实现函数作为 mock 实现。
如果 mock 是使用 vi.spyOn
创建的,除非提供了自定义实现,否则返回 undefined
。
getMockName
function getMockName(): string;
用于返回使用 .mockName(name)
方法分配给 mock 的名称。默认情况下,返回 vi.fn()
。
mockClear
function mockClear(): MockInstance<T>;
清除每次调用的所有信息。调用此方法后,.mock
上的所有属性将恢复到其初始状态。此方法不会重置实现。对于在不同断言之间清理模拟对象非常有用。
要在每次测试之前自动调用此方法,请在配置中启用 clearMocks
设置。
mockName
function mockName(name: string): MockInstance<T>;
设置内部模拟名称。这有助于在断言失败时识别 mock。
mockImplementation
function mockImplementation(fn: T): MockInstance<T>;
接受一个函数作为 mock 实现。TypeScript 要求参数和返回类型与原始函数一致。
const mockFn = vi.fn().mockImplementation((apples: number) => apples + 1);
// 或者:vi.fn(apples => apples + 1);
const NelliesBucket = mockFn(0);
const BobsBucket = mockFn(1);
NelliesBucket === 1; // true
BobsBucket === 2; // true
mockFn.mock.calls[0][0] === 0; // true
mockFn.mock.calls[1][0] === 1; // true
mockImplementationOnce
function mockImplementationOnce(fn: T): MockInstance<T>;
接受一个函数作为 mock 实现。TypeScript 要求参数和返回类型与原始函数一致。此方法可以链式调用,为多次函数调用提供不同的结果。
const myMockFn = vi
.fn()
.mockImplementationOnce(() => true) // 第一次调用
.mockImplementationOnce(() => false); // 第二次调用
myMockFn(); // 第一次调用:true
myMockFn(); // 第二次调用:false
当模拟函数没有更多一次性实现时,如果设置了默认实现(通过 vi.fn(() => defaultValue)
或 .mockImplementation(() => defaultValue)
),将调用默认实现:
const myMockFn = vi
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
withImplementation
function withImplementation(fn: T, cb: () => void): MockInstance<T>;
function withImplementation(
fn: T,
cb: () => Promise<void>
): Promise<MockInstance<T>>;
在回调执行期间临时覆盖原始 mock 实现。
const myMockFn = vi.fn(() => 'original');
myMockFn.withImplementation(
() => 'temp',
() => {
myMockFn(); // 'temp'
}
);
myMockFn(); // 'original'
可以与异步回调一起使用。需要等待该方法执行完毕才能恢复使用原始实现。
test('async callback', async () => {
const myMockFn = vi.fn(() => 'original');
// 我们等待此调用,因为回调是异步的
await myMockFn.withImplementation(
() => 'temp',
async () => {
myMockFn(); // 'temp'
}
);
myMockFn(); // 'original'
});
请注意,此方法优先于 mockImplementationOnce
。
mockRejectedValue
function mockRejectedValue(value: unknown): MockInstance<T>;
接受一个错误,当调用异步函数时将会被拒绝。
const asyncMock = vi.fn().mockRejectedValue(new Error('Async error'));
await asyncMock(); // 抛出 Error<'Async error'>
mockRejectedValueOnce
function mockRejectedValueOnce(value: unknown): MockInstance<T>;
接受一个值,该值将在下一次函数调用期间被拒绝。如果链式调用,每次连续调用都将拒绝指定的值。
const asyncMock = vi
.fn()
.mockResolvedValueOnce('first call')
.mockRejectedValueOnce(new Error('Async error'));
await asyncMock(); // 'first call'
await asyncMock(); // 抛出 Error<'Async error'>
mockReset
function mockReset(): MockInstance<T>;
执行与 mockClear
相同的操作,并将内部实现设置为空函数(调用时返回 undefined
)。这也重置所有 "once" 实现。对于将 mock 完全重置为其默认状态非常有用。
要在每次测试之前自动调用此方法,请在配置中启用 mockReset
设置。
mockRestore
function mockRestore(): MockInstance<T>;
执行与 mockReset
相同的操作,并将内部实现恢复为原始函数。
请注意,恢复使用 vi.fn()
创建的 mock 将把实现设置为空函数,返回 undefined
。恢复使用 vi.fn(impl)
创建的 mock 将把实现恢复为 impl
。
要在每次测试之前自动调用此方法,请在配置中启用 restoreMocks
设置。
mockResolvedValue
function mockResolvedValue(value: Awaited<ReturnType<T>>): MockInstance<T>;
接受一个值,当调用异步函数时将被解析。TypeScript 只接受与原始函数返回类型匹配的值。
const asyncMock = vi.fn().mockResolvedValue(42);
await asyncMock(); // 42
mockResolvedValueOnce
function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockInstance<T>;
接受一个值,该值将在下一次函数调用期间被解析。TypeScript 只接受与原始函数返回类型匹配的值。如果链式调用,每次连续调用都将解析指定的值。
const asyncMock = vi
.fn()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call');
await asyncMock(); // first call
await asyncMock(); // second call
await asyncMock(); // default
await asyncMock(); // default
mockReturnThis
function mockReturnThis(): MockInstance<T>;
如果你需要从方法返回 this
上下文而不调用实际实现,请使用此方法。这是以下代码的简写:
spy.mockImplementation(function () {
return this;
});
mockReturnValue
function mockReturnValue(value: ReturnType<T>): MockInstance<T>;
接受一个值,无论何时调用 mock 函数都将返回该值。TypeScript 只接受与原始函数返回类型匹配的值。
const mock = vi.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
mockReturnValueOnce
function mockReturnValueOnce(value: ReturnType<T>): MockInstance<T>;
接受一个值,无论何时调用 mock 函数都将返回该值。TypeScript 只接受与原始函数返回类型匹配的值。
当模拟函数没有更多一次性实现时,如果调用了 vi.fn(() => defaultValue)
或 .mockImplementation(() => defaultValue)
,它将调用默认实现:
const myMockFn = vi
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
mock.calls
const calls: Parameters<T>[];
这是一个数组,包含每次调用的所有参数。数组中的一项是该次调用的参数数组。
const fn = vi.fn();
fn('arg1', 'arg2');
fn('arg3');
fn.mock.calls ===
[
['arg1', 'arg2'], // 第一次调用
['arg3'], // 第二次调用
];
mock.lastCall
const lastCall: Parameters<T> | undefined;
这包含最后一次调用的参数数组。如果 mock 未被调用,返回 undefined
。
mock.results
interface MockResultReturn<T> {
type: 'return';
/**
* 函数返回的值。
* 如果函数返回 Promise,则这将是解析后的值。
*/
value: T;
}
interface MockResultIncomplete {
type: 'incomplete';
value: undefined;
}
interface MockResultThrow {
type: 'throw';
/**
* 函数执行期间抛出的错误。
*/
value: any;
}
type MockResult<T> =
| MockResultReturn<T>
| MockResultThrow
| MockResultIncomplete;
const results: MockResult<ReturnType<T>>[];
这是一个数组,包含函数返回的所有结果。数组中的一项是一个包含 type
和 value
属性的对象。可用类型有:
'return'
- 函数正常返回而未抛出异常。'throw'
- 函数抛出了异常。
value
属性包含返回值或抛出的错误。如果函数返回 Promise
,即使是 Promise 被拒绝,result
的 type
也始终是 'return'
。
const fn = vi
.fn()
.mockReturnValueOnce('result')
.mockImplementationOnce(() => {
throw new Error('thrown error');
});
const result = fn(); // 返回 'result'
try {
fn(); // 抛出 Error
} catch {}
fn.mock.results ===
[
// 第一个结果
{
type: 'return',
value: 'result',
},
// 最后一个结果
{
type: 'throw',
value: Error,
},
];
mock.settledResults
interface MockSettledResultFulfilled<T> {
type: 'fulfilled';
value: T;
}
interface MockSettledResultRejected {
type: 'rejected';
value: any;
}
export type MockSettledResult<T> =
| MockSettledResultFulfilled<T>
| MockSettledResultRejected;
const settledResults: MockSettledResult<Awaited<ReturnType<T>>>[];
一个数组,包含函数解析或拒绝的所有值。
如果函数从未被解析或拒绝,则此数组将为空。
const fn = vi.fn().mockResolvedValueOnce('result');
const result = fn();
fn.mock.settledResults === [];
await result;
fn.mock.settledResults ===
[
{
type: 'fulfilled',
value: 'result',
},
];
mock.invocationCallOrder
const invocationCallOrder: number[];
此属性返回模拟函数的执行顺序。它是一个数字数组,在所有已定义的 mock 之间共享。
const fn1 = vi.fn();
const fn2 = vi.fn();
fn1();
fn2();
fn1();
fn1.mock.invocationCallOrder === [1, 3];
fn2.mock.invocationCallOrder === [2];
mock.contexts
const contexts: ThisParameterType<T>[];
此属性是一个数组,包含在每次调用模拟函数期间使用的 this
值。
const fn = vi.fn();
const context = {};
fn.apply(context);
fn.call(context);
fn.mock.contexts[0] === context;
fn.mock.contexts[1] === context;
mock.instances
const instances: ReturnType<T>[];
此属性是一个数组,包含当使用 new
关键字调用模拟函数时创建的所有实例。请注意,这是函数的实际上下文 (this
),而不是返回值。
WARNING
如果 mock 是使用 new MyClass()
实例化的,则 mock.instances
将是一个包含一个值的数组:
const MyClass = vi.fn();
const a = new MyClass();
MyClass.mock.instances[0] === a;
如果你从构造函数返回一个值,它将不会在 instances
数组中,而是在 results
中:
const Spy = vi.fn(() => ({ method: vi.fn() }));
const a = new Spy();
Spy.mock.instances[0] !== a;
Spy.mock.results[0] === a;