Skip to main content
Version: 29.7

模拟函数


模拟函数也称为 "spies",因为它们可以让你监视由其他代码间接调用的函数的行为,而不仅仅是测试输出。 你可以使用 jest.fn() 创建模拟函数。 如果没有给出实现,则模拟函数在调用时将返回 undefined

信息

仅当你显式导入 Jest API 时,此页面中的 TypeScript 示例才会按照文档说明的方式工作:

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

有关如何使用 TypeScript 设置 Jest 的详细信息,请参阅 入门 指南。

方法


参考

mockFn.getMockName()

返回通过调用 .mockName() 设置的模拟名称字符串。

mockFn.mock.calls

包含对此模拟函数进行的所有调用的调用参数的数组。 数组中的每一项都是调用期间传递的参数数组。

例如: 已使用参数 f('arg1', 'arg2')、然后使用参数 f('arg3', 'arg4') 调用两次的模拟函数 f 将具有如下所示的 mock.calls 数组:

[
['arg1', 'arg2'],
['arg3', 'arg4'],
];

mockFn.mock.results

包含对此模拟函数进行的所有调用的结果的数组。 该数组中的每个条目都是一个包含 type 属性和 value 属性的对象。 type 将是以下之一:

  • 'return' - 表示调用完成并正常返回。
  • 'throw' - 表明调用通过抛出一个值来完成。
  • 'incomplete' - 表示调用尚未完成。 如果你从模拟函数本身内部或从模拟调用的函数内部测试结果,就会发生这种情况。

value 属性包含抛出或返回的值。 当 type === 'incomplete'value 未定义。

例如: 一个模拟函数 f 被调用了 3 次,返回 'result1',抛出错误,然后返回 'result2',将有一个如下所示的 mock.results 数组:

[
{
type: 'return',
value: 'result1',
},
{
type: 'throw',
value: {
/* Error instance */
},
},
{
type: 'return',
value: 'result2',
},
];

mockFn.mock.instances

一个数组,包含已使用 new 从该模拟函数实例化的所有对象实例。

例如: 已实例化两次的模拟函数将具有以下 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

包含模拟函数的所有调用的上下文的数组。

上下文是函数调用时接收的 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

包含对此模拟函数进行的最后一次调用的调用参数的数组。 如果该函数没有被调用,它将返回 undefined

例如: 已使用参数 f('arg1', 'arg2')、然后使用参数 f('arg3', 'arg4') 调用两次的模拟函数 f 将具有如下所示的 mock.lastCall 数组:

['arg3', 'arg4'];

mockFn.mockClear()

清除 mockFn.mock.callsmockFn.mock.instancesmockFn.mock.contextsmockFn.mock.results 数组中存储的所有信息。 当你想要清理两个断言之间的模拟使用数据时,这通常很有用。

clearMocks 配置选项可用于在每次测试之前自动清除模拟。

提醒

请注意,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)

接受应该用作模拟实现的函数。 模拟本身仍然会记录所有进入其本身的调用和来自其自身的实例 – 唯一的区别是,当调用 mock 时,实现也会被执行。

提示

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 用法

信息

仅当你显式导入 Jest API 时,此页面中的 TypeScript 示例才会按照文档说明的方式工作:

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

有关如何使用 TypeScript 设置 Jest 的详细信息,请参阅 入门 指南。

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() 辅助方法使用 Jest 模拟函数的类型定义封装 source 对象及其深层嵌套成员的类型。 你可以将 {shallow: true} 作为 options 参数传递以禁用深度模拟行为。

返回 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, 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 的类型。