Mock 函数
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
Mock 函数也被称为"间谍函数",因为它们允许你监视被其他代码间接调用的函数行为,而不仅仅是测试输出结果。你可以使用 jest.fn() 创建 mock 函数。如果未提供实现,调用时将返回 undefined。
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
本页中的 TypeScript 示例仅在显式导入 Jest API 的情况下才能按文档所述正常工作:
import {expect, jest, test} from '@jest/globals';
有关如何设置 Jest 与 TypeScript 配合使用的详细信息,请查阅入门指南。
方法
- 参数详解
mockFn.getMockName()mockFn.mock.callsmockFn.mock.resultsmockFn.mock.instancesmockFn.mock.contextsmockFn.mock.lastCallmockFn.mockClear()mockFn.mockReset()mockFn.mockRestore()mockFn.mockImplementation(fn)mockFn.mockImplementationOnce(fn)mockFn.mockName(name)mockFn.mockReturnThis()mockFn.mockReturnValue(value)mockFn.mockReturnValueOnce(value)mockFn.mockResolvedValue(value)mockFn.mockResolvedValueOnce(value)mockFn.mockRejectedValue(value)mockFn.mockRejectedValueOnce(value)mockFn.withImplementation(fn, callback)
- 被替换属性
- TypeScript 用法
参数详解
mockFn.getMockName()
返回通过调用 .mockName() 设置的 mock 名称字符串。
mockFn.mock.calls
包含该 mock 函数所有调用参数的数组。每个数组元素是单次调用时传递的参数数组。
例如:mock 函数 f 被调用两次,参数分别为 f('arg1', 'arg2') 和 f('arg3', 'arg4'),其 mock.calls 数组将如下所示:
[
['arg1', 'arg2'],
['arg3', 'arg4'],
];
mockFn.mock.results
包含该 mock 函数所有调用结果的数组。每个元素是包含 type 和 value 属性的对象,其中 type 可能为以下值之一:
-
'return'- 表示函数正常返回完成 -
'throw'- 表示函数抛出异常完成 -
'incomplete'- 表示调用尚未完成(当在 mock 函数内部或其调用函数中测试结果时发生)
value 属性包含抛出或返回的值。当 type === 'incomplete' 时 value 为 undefined。
例如:mock 函数 f 被调用三次,分别返回 'result1'、抛出错误、再返回 'result2',其 mock.results 数组将如下所示:
[
{
type: 'return',
value: 'result1',
},
{
type: 'throw',
value: {
/* Error instance */
},
},
{
type: 'return',
value: 'result2',
},
];
mockFn.mock.instances
包含通过 new 实例化的所有对象实例的数组。
例如:被实例化两次的 mock 函数,其 mock.instances 数组将如下所示:
const mockFn = jest.fn();
const a = new mockFn();
const b = new mockFn();
mockFn.mock.instances[0] === a; // true
mockFn.mock.instances[1] === b; // true
mockFn.mock.contexts
包含该 mock 函数所有调用上下文(this 值)的数组。
上下文是函数被调用时接收的 this 值,可通过 Function.prototype.bind、Function.prototype.call 或 Function.prototype.apply 设置。
例如:
const mockFn = jest.fn();
const boundMockFn = mockFn.bind(thisContext0);
boundMockFn('a', 'b');
mockFn.call(thisContext1, 'a', 'b');
mockFn.apply(thisContext2, ['a', 'b']);
mockFn.mock.contexts[0] === thisContext0; // true
mockFn.mock.contexts[1] === thisContext1; // true
mockFn.mock.contexts[2] === thisContext2; // true
mockFn.mock.lastCall
包含该 mock 函数最后一次调用参数的数组。若函数未被调用则返回 undefined。
例如:mock 函数 f 被调用两次,参数分别为 f('arg1', 'arg2') 和 f('arg3', 'arg4'),其 mock.lastCall 数组将如下所示:
['arg3', 'arg4'];
mockFn.mockClear()
清除 mockFn.mock.calls、mockFn.mock.instances、mockFn.mock.contexts 和 mockFn.mock.results 数组中的所有信息。这在两个断言之间清理 mock 使用数据时特别有用。
可通过 clearMocks 配置选项在每次测试前自动清除 mock。
请注意:mockFn.mockClear() 会直接替换整个 mockFn.mock 对象,而非仅重置其属性值!因此应避免将 mockFn.mock 赋值给其他变量(无论临时与否),以防止访问到过期数据。
mockFn.mockReset()
执行 mockFn.mockClear() 的所有操作,并额外将模拟实现替换为空函数(返回 undefined)。
可通过 resetMocks 配置选项在每个测试前自动重置模拟函数。
mockFn.mockRestore()
执行 mockFn.mockReset() 的所有操作,并额外恢复原始(非模拟)实现。
此方法适用于需要在特定测试用例中模拟函数,并在其他用例中恢复原始实现的场景。
可通过 restoreMocks 配置选项在每个测试前自动恢复模拟函数。
mockFn.mockRestore() 仅对通过 jest.spyOn() 创建的模拟生效。若手动使用 jest.fn() 赋值,需自行处理恢复操作。
mockFn.mockImplementation(fn)
接受一个函数作为模拟实现。该模拟函数仍会记录所有调用信息及自身产生的实例——唯一区别在于调用时将执行此实现函数。
jest.fn(implementation) 等价于 jest.fn().mockImplementation(implementation) 的简写形式。
- JavaScript
- TypeScript
const mockFn = jest.fn(scalar => 42 + scalar);
mockFn(0); // 42
mockFn(1); // 43
mockFn.mockImplementation(scalar => 36 + scalar);
mockFn(2); // 38
mockFn(3); // 39
import {jest} from '@jest/globals';
const mockFn = jest.fn((scalar: number) => 42 + scalar);
mockFn(0); // 42
mockFn(1); // 43
mockFn.mockImplementation(scalar => 36 + scalar);
mockFn(2); // 38
mockFn(3); // 39
.mockImplementation() 也可用于模拟类构造函数:
- JavaScript
- TypeScript
module.exports = class SomeClass {
method(a, b) {}
};
const SomeClass = require('./SomeClass');
jest.mock('./SomeClass'); // this happens automatically with automocking
const mockMethod = jest.fn();
SomeClass.mockImplementation(() => {
return {
method: mockMethod,
};
});
const some = new SomeClass();
some.method('a', 'b');
console.log('Calls to method:', mockMethod.mock.calls);
export class SomeClass {
method(a: string, b: string): void {}
}
import {jest} from '@jest/globals';
import {SomeClass} from './SomeClass';
jest.mock('./SomeClass'); // this happens automatically with automocking
const mockMethod = jest.fn<(a: string, b: string) => void>();
jest.mocked(SomeClass).mockImplementation(() => {
return {
method: mockMethod,
};
});
const some = new SomeClass();
some.method('a', 'b');
console.log('Calls to method:', mockMethod.mock.calls);
mockFn.mockImplementationOnce(fn)
接受一个函数作为单次调用的模拟实现。可通过链式调用为多次函数调用生成不同结果。
- JavaScript
- TypeScript
const mockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
mockFn((err, val) => console.log(val)); // true
mockFn((err, val) => console.log(val)); // false
import {jest} from '@jest/globals';
const mockFn = jest
.fn<(cb: (a: null, b: boolean) => void) => void>()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
mockFn((err, val) => console.log(val)); // true
mockFn((err, val) => console.log(val)); // false
当模拟函数耗尽所有 .mockImplementationOnce() 定义后,将执行通过 jest.fn(() => defaultValue) 或 .mockImplementation(() => defaultValue) 设置的默认实现:
const mockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'
mockFn.mockName(name)
接受字符串参数,用于在测试结果中替代 'jest.fn()' 标识具体模拟函数。
例如:
const mockFn = jest.fn().mockName('mockedFunction');
// mockFn();
expect(mockFn).toHaveBeenCalled();
将产生如下错误提示:
expect(mockedFunction).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
mockFn.mockReturnThis()
简写形式:
jest.fn(function () {
return this;
});
mockFn.mockReturnValue(value)
简写形式:
jest.fn().mockImplementation(() => value);
接受一个值,该值将在每次调用模拟函数时返回。
- JavaScript
- TypeScript
const mock = jest.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
import {jest} from '@jest/globals';
const mock = jest.fn<() => number>();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43
mockFn.mockReturnValueOnce(value)
简写形式:
jest.fn().mockImplementationOnce(() => value);
接受一个值作为单次调用的返回值。可通过链式调用使连续调用返回不同值。当所有 mockReturnValueOnce 值耗尽后,将返回 mockReturnValue 指定的值。
- JavaScript
- TypeScript
const mockFn = jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'
import {jest} from '@jest/globals';
const mockFn = jest
.fn<() => string>()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'
mockFn.mockResolvedValue(value)
简写形式:
jest.fn().mockImplementation(() => Promise.resolve(value));
适用于在异步测试中模拟异步函数:
- JavaScript
- TypeScript
test('async test', async () => {
const asyncMock = jest.fn().mockResolvedValue(43);
await asyncMock(); // 43
});
import {jest, test} from '@jest/globals';
test('async test', async () => {
const asyncMock = jest.fn<() => Promise<number>>().mockResolvedValue(43);
await asyncMock(); // 43
});
mockFn.mockResolvedValueOnce(value)
简写形式:
jest.fn().mockImplementationOnce(() => Promise.resolve(value));
适用于在多次异步调用中返回不同结果:
- JavaScript
- TypeScript
test('async test', async () => {
const asyncMock = jest
.fn()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call');
await asyncMock(); // 'first call'
await asyncMock(); // 'second call'
await asyncMock(); // 'default'
await asyncMock(); // 'default'
});
import {jest, test} from '@jest/globals';
test('async test', async () => {
const asyncMock = jest
.fn<() => Promise<string>>()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call');
await asyncMock(); // 'first call'
await asyncMock(); // 'second call'
await asyncMock(); // 'default'
await asyncMock(); // 'default'
});
mockFn.mockRejectedValue(value)
简写形式:
jest.fn().mockImplementation(() => Promise.reject(value));
适用于创建始终拒绝的异步模拟函数:
- JavaScript
- TypeScript
test('async test', async () => {
const asyncMock = jest
.fn()
.mockRejectedValue(new Error('Async error message'));
await asyncMock(); // throws 'Async error message'
});
import {jest, test} from '@jest/globals';
test('async test', async () => {
const asyncMock = jest
.fn<() => Promise<never>>()
.mockRejectedValue(new Error('Async error message'));
await asyncMock(); // throws 'Async error message'
});
mockFn.mockRejectedValueOnce(value)
简写形式:
jest.fn().mockImplementationOnce(() => Promise.reject(value));
与 .mockResolvedValueOnce() 配合使用,或在多次异步调用中抛出不同异常时特别实用:
- JavaScript
- TypeScript
test('async test', async () => {
const asyncMock = jest
.fn()
.mockResolvedValueOnce('first call')
.mockRejectedValueOnce(new Error('Async error message'));
await asyncMock(); // 'first call'
await asyncMock(); // throws 'Async error message'
});
import {jest, test} from '@jest/globals';
test('async test', async () => {
const asyncMock = jest
.fn<() => Promise<string>>()
.mockResolvedValueOnce('first call')
.mockRejectedValueOnce(new Error('Async error message'));
await asyncMock(); // 'first call'
await asyncMock(); // throws 'Async error message'
});
mockFn.withImplementation(fn, callback)
接受一个函数作为临时模拟实现,该实现将在回调函数执行期间生效。
test('test', () => {
const mock = jest.fn(() => 'outside callback');
mock.withImplementation(
() => 'inside callback',
() => {
mock(); // 'inside callback'
},
);
mock(); // 'outside callback'
});
无论回调函数是否异步(返回 thenable 对象),均可使用 mockFn.withImplementation。若回调为异步函数则返回 Promise。等待该 Promise 将执行回调并重置模拟实现。
test('async test', async () => {
const mock = jest.fn(() => 'outside callback');
// We await this call since the callback is async
await mock.withImplementation(
() => 'inside callback',
async () => {
mock(); // 'inside callback'
},
);
mock(); // 'outside callback'
});
被替换属性
replacedProperty.replaceValue(value)
修改已替换属性的值。这在需要先替换属性再针对特定测试调整值的场景非常实用。替代方案是在同一属性上多次调用 jest.replaceProperty()。
replacedProperty.restore()
将对象的属性恢复为原始值。
注意:replacedProperty.restore() 仅对通过 jest.replaceProperty() 替换的属性生效。
配置项 restoreMocks 可在每次测试前自动恢复被替换属性。
TypeScript 用法
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
本页中的 TypeScript 示例仅在显式导入 Jest API 的情况下才能按文档所述正常工作:
import {expect, jest, test} from '@jest/globals';
有关如何设置 Jest 与 TypeScript 配合使用的详细信息,请查阅入门指南。
jest.fn(implementation?)
若向 jest.fn() 传递实现函数,系统将自动推导正确的模拟类型。但许多场景会省略实现函数。为确保类型安全,可传递泛型类型参数(参考前文示例):
import {expect, jest, test} from '@jest/globals';
import type add from './add';
import calculate from './calc';
test('calculate calls add', () => {
// Create a new mock that can be used in place of `add`.
const mockAdd = jest.fn<typeof add>();
// `.mockImplementation()` now can infer that `a` and `b` are `number`
// and that the returned value is a `number`.
mockAdd.mockImplementation((a, b) => {
// Yes, this mock is still adding two numbers but imagine this
// was a complex function we are mocking.
return a + b;
});
// `mockAdd` is properly typed and therefore accepted by anything
// requiring `add`.
calculate(mockAdd, 1, 2);
expect(mockAdd).toHaveBeenCalledTimes(1);
expect(mockAdd).toHaveBeenCalledWith(1, 2);
});
jest.Mock<T>
构建模拟函数类型(例如 jest.fn() 的返回类型)。定义递归模拟函数时特别实用:
import {jest} from '@jest/globals';
const sumRecursively: jest.Mock<(value: number) => number> = jest.fn(value => {
if (value === 0) {
return 0;
} else {
return value + fn(value - 1);
}
});
jest.Mocked<Source>
jest.Mocked<Source> 工具类型返回封装了 Jest 模拟函数类型定义的 Source 类型。
import {expect, jest, test} from '@jest/globals';
import type {fetch} from 'node-fetch';
jest.mock('node-fetch');
let mockedFetch: jest.Mocked<typeof fetch>;
afterEach(() => {
mockedFetch.mockClear();
});
test('makes correct call', () => {
mockedFetch = getMockedFetch();
// ...
});
test('returns correct data', () => {
mockedFetch = getMockedFetch();
// ...
});
类、函数或对象的类型均可作为类型参数传递给 jest.Mocked<Source>。若需约束输入类型,请使用:jest.MockedClass<Source>、jest.MockedFunction<Source> 或 jest.MockedObject<Source>。
jest.Replaced<Source>
jest.Replaced<Source> 工具类型返回封装了 Jest 被替换属性 类型定义的 Source 类型。
export function isLocalhost(): boolean {
return process.env['HOSTNAME'] === 'localhost';
}
import {afterEach, expect, it, jest} from '@jest/globals';
import {isLocalhost} from '../utils';
let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined;
afterEach(() => {
replacedEnv?.restore();
});
it('isLocalhost should detect localhost environment', () => {
replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'});
expect(isLocalhost()).toBe(true);
});
it('isLocalhost should detect non-localhost environment', () => {
replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'});
expect(isLocalhost()).toBe(false);
});
jest.mocked(source, options?)
mocked() 辅助方法将 source 对象及其深层嵌套成员的类型封装为 Jest 模拟函数类型定义。可通过 options 参数传递 {shallow: true} 来禁用深度模拟行为。
返回 source 对象。
export const song = {
one: {
more: {
time: (t: number) => {
return t;
},
},
},
};
import {expect, jest, test} from '@jest/globals';
import {song} from './song';
jest.mock('./song');
jest.spyOn(console, 'log');
const mockedSong = jest.mocked(song);
// or through `jest.Mocked<Source>`
// const mockedSong = song as jest.Mocked<typeof song>;
test('deep method is typed correctly', () => {
mockedSong.one.more.time.mockReturnValue(12);
expect(mockedSong.one.more.time(10)).toBe(12);
expect(mockedSong.one.more.time.mock.calls).toHaveLength(1);
});
test('direct usage', () => {
jest.mocked(console.log).mockImplementation(() => {
return;
});
console.log('one more time');
expect(jest.mocked(console.log).mock.calls).toHaveLength(1);
});
jest.Spied<Source>
构建被监视类或函数的类型(即 jest.spyOn() 的返回类型)。
import {jest} from '@jest/globals';
export function setDateNow(now: number): jest.Spied<typeof Date.now> {
return jest.spyOn(Date, 'now').mockReturnValue(now);
}
import {afterEach, expect, type jest, test} from '@jest/globals';
import {setDateNow} from './__utils__/setDateNow';
let spiedDateNow: jest.Spied<typeof Date.now> | undefined = undefined;
afterEach(() => {
spiedDateNow?.mockReset();
});
test('renders correctly with a given date', () => {
spiedDateNow = setDateNow(1_482_363_367_071);
// ...
expect(spiedDateNow).toHaveBeenCalledTimes(1);
});
类或函数的类型可作为类型参数传递给 jest.Spied<Source>。若需约束输入类型,请使用:jest.SpiedClass<Source> 或 jest.SpiedFunction<Source>。
分别使用 jest.SpiedGetter<Source> 或 jest.SpiedSetter<Source> 创建被监视 getter 或 setter 的类型。