模拟函数
模拟函数也称为“间谍”,因为它们允许你监视间接由其他代码调用的函数的行为,而不仅仅是测试输出。你可以使用 jest.fn()
创建模拟函数。如果没有给出实现,模拟函数在调用时将返回 undefined
。
本页面的 TypeScript 示例只有在显式导入 Jest API 时才会按文档工作
import {expect, jest, test} from '@jest/globals';
有关如何使用 TypeScript 设置 Jest 的详细信息,请参阅 入门 指南。
方法
- 参考
mockFn.getMockName()
mockFn.mock.calls
mockFn.mock.results
mockFn.mock.instances
mockFn.mock.contexts
mockFn.mock.lastCall
mockFn.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()
设置的模拟名称字符串。
mockFn.mock.calls
一个数组,包含对该模拟函数进行的所有调用的调用参数。数组中的每个项目都是调用期间传递的参数数组。
例如:一个模拟函数 f
,它已被调用两次,参数为 f('arg1', 'arg2')
,然后参数为 f('arg3', 'arg4')
,将有一个 mock.calls
数组,如下所示
[
['arg1', 'arg2'],
['arg3', 'arg4'],
];
mockFn.mock.results
一个数组,包含对该模拟函数进行的所有调用的结果。此数组中的每个条目都是一个包含 type
属性和 value
属性的对象。type
将是以下之一
'return'
- 表示调用通过正常返回完成。'throw'
- 表示调用通过抛出值完成。'incomplete'
- 表示调用尚未完成。如果在模拟函数本身内部或在模拟函数调用的函数内部测试结果,就会发生这种情况。
value
属性包含抛出或返回的值。当 type === 'incomplete'
时,value
未定义。
例如:一个模拟函数 f
,它已被调用三次,返回 '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.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
一个数组,包含对该模拟函数进行的最后一次调用的调用参数。如果函数未被调用,它将返回 undefined
。
例如:一个模拟函数 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
数组中的所有信息。通常,当你想在两个断言之间清理模拟的使用数据时,这很有用。
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)
接受一个函数,该函数应作为模拟的实现使用。模拟本身仍然会记录进入它的所有调用和来自它的所有实例 - 唯一的区别是,当调用模拟时,实现也会被执行。
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()
mockFn.mockReturnValue(value)
的简写
jest.fn(function () {
return this;
});
mockFn.mockReturnValue(value)
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)
mockFn.mockReturnValue(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)
mockFn.mockReturnValue(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)
mockFn.mockReturnValue(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)
mockFn.mockReturnValue(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)
mockFn.mockReturnValue(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'
});
mockFn.withImplementation
可用于回调函数是否异步(返回 thenable
)。如果回调函数是异步的,则会返回一个 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 使用
本页面的 TypeScript 示例只有在显式导入 Jest API 时才会按文档工作
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
类型。
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()
辅助方法使用 Jest 模拟函数类型定义包装 source
对象及其深层嵌套成员的类型。您可以将 {shallow: true}
作为 options
参数传递以禁用深度模拟行为。
返回 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 的类型。