跳至主要內容
版本:29.7

模擬函式

模擬函式也稱為「間諜」,因為它們可以讓你監視間接由其他程式碼呼叫的函式行為,而不仅仅是測試輸出。你可以使用 jest.fn() 建立模擬函式。如果未提供實作,則模擬函式在呼叫時會傳回 undefined

資訊

此頁面的 TypeScript 範例只有在你明確匯入 Jest API 時才會如文件所述運作

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

參閱 入門 指南,了解如何使用 TypeScript 設定 Jest 的詳細資訊。

方法


參考

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.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 已呼叫兩次,參數為 f('arg1', 'arg2'),然後參數為 f('arg3', 'arg4'),它的 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)

接受一個函式,該函式應作為模擬的實作。模擬本身仍會記錄所有進入和來自本身的呼叫和實例,唯一的差別是呼叫模擬時也會執行實作。

提示

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)

接受一個函式,該函式應暫時用作模擬函式的實作,同時執行 callback。

test('test', () => {
const mock = jest.fn(() => 'outside callback');

mock.withImplementation(
() => 'inside callback',
() => {
mock(); // 'inside callback'
},
);

mock(); // 'outside callback'
});

mockFn.withImplementation 可以使用,無論 callback 是否非同步(回傳一個 thenable)。如果 callback 是非同步的,將會回傳一個 Promise。等待 Promise 將會等待 callback 並重設實作。

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

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, 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 的類型。