跳至主内容
版本:30.0

Mock 函数

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Mock 函数也被称为"间谍函数",因为它们允许你监视被其他代码间接调用的函数行为,而不仅仅是测试输出结果。你可以使用 jest.fn() 创建 mock 函数。如果未提供实现,调用时将返回 undefined

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

信息

本页中的 TypeScript 示例仅在显式导入 Jest API 的情况下才能按文档所述正常工作:

import {expect, jest, test} from '@jest/globals';

有关如何设置 Jest 与 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 函数所有调用结果的数组。每个元素是包含 typevalue 属性的对象,其中 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.bindFunction.prototype.callFunction.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.callsmockFn.mock.instancesmockFn.mock.contextsmockFn.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) 的简写形式。

const mockFn = jest.fn(scalar => 42 + scalar);

mockFn(0); // 42
mockFn(1); // 43

mockFn.mockImplementation(scalar => 36 + scalar);

mockFn(2); // 38
mockFn(3); // 39

.mockImplementation() 也可用于模拟类构造函数:

SomeClass.js
module.exports = class SomeClass {
method(a, b) {}
};
SomeClass.test.js
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);

mockFn.mockImplementationOnce(fn)

接受一个函数作为单次调用的模拟实现。可通过链式调用为多次函数调用生成不同结果。

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

当模拟函数耗尽所有 .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);

接受一个值,该值将在每次调用模拟函数时返回。

const mock = jest.fn();

mock.mockReturnValue(42);
mock(); // 42

mock.mockReturnValue(43);
mock(); // 43

mockFn.mockReturnValueOnce(value)

简写形式:

jest.fn().mockImplementationOnce(() => value);

接受一个值作为单次调用的返回值。可通过链式调用使连续调用返回不同值。当所有 mockReturnValueOnce 值耗尽后,将返回 mockReturnValue 指定的值。

const mockFn = jest
.fn()
.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));

适用于在异步测试中模拟异步函数:

test('async test', async () => {
const asyncMock = jest.fn().mockResolvedValue(43);

await asyncMock(); // 43
});

mockFn.mockResolvedValueOnce(value)

简写形式:

jest.fn().mockImplementationOnce(() => Promise.resolve(value));

适用于在多次异步调用中返回不同结果:

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'
});

mockFn.mockRejectedValue(value)

简写形式:

jest.fn().mockImplementation(() => Promise.reject(value));

适用于创建始终拒绝的异步模拟函数:

test('async test', async () => {
const asyncMock = jest
.fn()
.mockRejectedValue(new Error('Async error message'));

await asyncMock(); // throws 'Async error message'
});

mockFn.mockRejectedValueOnce(value)

简写形式:

jest.fn().mockImplementationOnce(() => Promise.reject(value));

.mockResolvedValueOnce() 配合使用,或在多次异步调用中抛出不同异常时特别实用:

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'
});

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 类型。

src/utils.ts
export function isLocalhost(): boolean {
return process.env['HOSTNAME'] === 'localhost';
}
src/__tests__/utils.test.ts
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 对象。

song.ts
export const song = {
one: {
more: {
time: (t: number) => {
return t;
},
},
},
};
song.test.ts
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() 的返回类型)。

__utils__/setDateNow.ts
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 的类型。