模擬函式
模擬函式也稱為「間諜」,因為它們可以讓你監視間接由其他程式碼呼叫的函式行為,而不仅仅是測試輸出。你可以使用 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()
簡寫為
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)
接受一個函式,該函式應暫時用作模擬函式的實作,同時執行 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
類型。
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 的類型。