模拟函数
你可以使用 vi.fn
方法创建一个模拟函数来追踪其调用。如果你想追踪已创建对象的方法,可以使用 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;
返回当前的模拟实现(如果存在)。
如果模拟是使用 vi.fn
创建的,它将使用提供的方法作为模拟实现。
如果模拟是使用 vi.spyOn
创建的,除非提供了自定义实现,否则它将返回 undefined
。
getMockName
function getMockName(): string;
使用此方法返回通过 .mockName(name)
方法分配给模拟的名称。默认情况下,它将返回 vi.fn()
。
mockClear
function mockClear(): MockInstance<T>;
清除每次调用的所有信息。调用此方法后,.mock
上的所有属性都将恢复到其初始状态。此方法不会重置实现。它对于在不同断言之间清理模拟很有用。
const person = {
greet: (name: string) => `Hello ${name}`,
};
const spy = vi.spyOn(person, 'greet').mockImplementation(() => 'mocked');
expect(person.greet('Alice')).toBe('mocked');
expect(spy.mock.calls).toEqual([['Alice']]);
// 清除调用历史,但保留模拟实现
spy.mockClear();
expect(spy.mock.calls).toEqual([]);
expect(person.greet('Bob')).toBe('mocked');
expect(spy.mock.calls).toEqual([['Bob']]);
要自动在每次测试之前调用此方法,请在配置中启用 clearMocks
设置。
mockName
function mockName(name: string): MockInstance<T>;
设置内部模拟名称。这在断言失败时有助于识别模拟函数。
mockImplementation
function mockImplementation(fn: T): MockInstance<T>;
接受一个函数作为模拟实现。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>;
接受一个函数作为模拟实现。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>>;
在回调执行期间暂时覆盖原始模拟实现。
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>;
接受一个错误,当异步函数被调用时,该错误将导致 Promise 被拒绝。
const asyncMock = vi.fn().mockRejectedValue(new Error('Async error'));
await asyncMock(); // 抛出 Error<'Async error'>
mockRejectedValueOnce
function mockRejectedValueOnce(value: unknown): MockInstance<T>;
接受一个值,该值将在下一次函数调用期间导致 Promise 被拒绝。如果链式调用,每次连续调用都将拒绝指定的值。
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
的功能,并将内部实现重置为原始函数。 这也会重置所有“一次性”实现。
请注意,重置通过 vi.fn()
创建的模拟函数会将实现设置为返回 undefined
的空函数。 重置 vi.fn(impl)
的模拟会将实现恢复为 impl
。
当您想要将模拟重置为其原始状态时,此方法很有用。
const person = {
greet: (name: string) => `Hello ${name}`,
};
const spy = vi.spyOn(person, 'greet').mockImplementation(() => 'mocked');
expect(person.greet('Alice')).toBe('mocked');
expect(spy.mock.calls).toEqual([['Alice']]);
// 清除调用历史并重置实现,但方法仍被监视
spy.mockReset();
expect(spy.mock.calls).toEqual([]);
expect(person.greet).toBe(spy);
expect(person.greet('Bob')).toBe('Hello Bob');
expect(spy.mock.calls).toEqual([['Bob']]);
要自动在每次测试之前调用此方法,请在配置中启用 mockReset
设置。
mockRestore
function mockRestore(): MockInstance<T>;
执行 mockReset
的功能,并恢复被监视对象的原始方法。
请注意,恢复 vi.fn()
的模拟会将实现设置为返回 undefined
的空函数。 恢复 vi.fn(impl)
的模拟会将实现恢复为 impl
。
const person = {
greet: (name: string) => `Hello ${name}`,
};
const spy = vi.spyOn(person, 'greet').mockImplementation(() => 'mocked');
expect(person.greet('Alice')).toBe('mocked');
expect(spy.mock.calls).toEqual([['Alice']]);
// 清除调用历史并恢复被监视对象的方法
spy.mockRestore();
expect(spy.mock.calls).toEqual([]);
expect(person.greet).not.toBe(spy);
expect(person.greet('Bob')).toBe('Hello Bob');
expect(spy.mock.calls).toEqual([]);
要自动在每次测试之前调用此方法,请在配置中启用 restoreMocks
设置。
mockResolvedValue
function mockResolvedValue(value: Awaited<ReturnType<T>>): MockInstance<T>;
接受一个值,当异步函数被调用时,该值将作为 Promise 的解决值。TypeScript 只接受与原始函数返回类型匹配的值。
const asyncMock = vi.fn().mockResolvedValue(42);
await asyncMock(); // 42
mockResolvedValueOnce
function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockInstance<T>;
接受一个值,该值将在下一次函数调用期间作为 Promise 的解决值。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>;
接受一个值,该值将在每次调用模拟函数时返回。TypeScript 只接受与原始函数返回类型匹配的值。
const mock = vi.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
mockReturnValueOnce
function mockReturnValueOnce(value: ReturnType<T>): MockInstance<T>;
接受一个值,该值将在每次调用模拟函数时返回。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;
此属性包含最后一次调用的参数。如果模拟未被调用,它将返回 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
,那么其结果的 type
将始终是 'return'
,即使该 Promise 最终被拒绝。
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>>>[];
一个数组,包含函数返回的 Promise 最终解决或拒绝的所有值。
如果函数返回的 Promise 尚未解决或拒绝,则此数组将为空。
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[];
此属性返回模拟函数的执行顺序。它是一个数字数组,该顺序在所有已定义的模拟之间是共享的。
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
如果模拟是用 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;