使用 mock
函數建立模擬。
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());
test("random", async () => {
const val = random();
expect(val).toBeGreaterThan(0);
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(1);
});
或者,您可以像 Jest 一樣使用 jest.fn()
函數。它的行為完全相同。
import { test, expect, jest } from "bun:test";
const random = jest.fn(() => Math.random());
test("random", async () => {
const val = random();
expect(val).toBeGreaterThan(0);
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(1);
});
mock()
的結果是一個新的函數,它被裝飾了一些額外的屬性。
import { mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());
random(2);
random(10);
random.mock.calls;
// [[ 2 ], [ 10 ]]
random.mock.results;
// [
// { type: "return", value: 0.6533907460954099 },
// { type: "return", value: 0.6452713933037312 }
// ]
以下屬性和方法在模擬函數上實作。
.spyOn()
可以追蹤函式的呼叫,而無需將其替換為 mock。使用 spyOn()
建立 spy;這些 spy 可以傳遞給 .toHaveBeenCalled()
和 .toHaveBeenCalledTimes()
。
import { test, expect, spyOn } from "bun:test";
const ringo = {
name: "Ringo",
sayHi() {
console.log(`Hello I'm ${this.name}`);
},
};
const spy = spyOn(ringo, "sayHi");
test("spyon", () => {
expect(spy).toHaveBeenCalledTimes(0);
ringo.sayHi();
expect(spy).toHaveBeenCalledTimes(1);
});
使用 mock.module()
的模組 mock
模組 mock 讓您可以覆寫模組的行為。使用 mock.module(path: string, callback: () => Object)
來 mock 模組。
import { test, expect, mock } from "bun:test";
mock.module("./module", () => {
return {
foo: "bar",
};
});
test("mock.module", async () => {
const esm = await import("./module");
expect(esm.foo).toBe("bar");
const cjs = require("./module");
expect(cjs.foo).toBe("bar");
});
與 Bun 的其他部分一樣,模組 mock 支援 import
和 require
。
覆寫已匯入的模組
如果您需要覆寫已匯入的模組,則無需執行任何特殊操作。只需呼叫 mock.module()
,模組就會被覆寫。
import { test, expect, mock } from "bun:test";
// The module we're going to mock is here:
import { foo } from "./module";
test("mock.module", async () => {
const cjs = require("./module");
expect(foo).toBe("bar");
expect(cjs.foo).toBe("bar");
// We update it here:
mock.module("./module", () => {
return {
foo: "baz",
};
});
// And the live bindings are updated.
expect(foo).toBe("baz");
// The module is also updated for CJS.
expect(cjs.foo).toBe("baz");
});
Hoisting & preloading
如果您需要確保模組在匯入之前被 mock,您應該使用 --preload
在測試執行之前載入您的 mock。
// my-preload.ts
import { mock } from "bun:test";
mock.module("./module", () => {
return {
foo: "bar",
};
});
bun test --preload ./my-preload
為了讓您的生活更輕鬆,您可以將 preload
放入您的 bunfig.toml
中
[test]
# Load these modules before running tests.
preload = ["./my-preload"]
如果我 mock 一個已經匯入的模組會發生什麼事?
如果您 mock 一個已經匯入的模組,則該模組將在模組快取中更新。這表示任何匯入該模組的模組都將取得 mock 版本,**但是**原始模組仍然會被評估。這表示來自原始模組的任何副作用仍然會發生。
如果您想防止原始模組被評估,您應該使用 --preload
在測試執行之前載入您的 mock。
__mocks__
目錄和自動 mock
目前尚不支援自動 mock。如果這阻礙您切換到 Bun,請提交 issue。
實作細節
模組 mock 對於 ESM 和 CommonJS 模組有不同的實作方式。對於 ES Modules,我們已將 patch 新增至 JavaScriptCore,讓 Bun 能夠在執行時覆寫匯出值並遞迴更新即時綁定。
從 Bun v1.0.19 開始,Bun 會自動解析 specifier
參數給 mock.module()
,就像您執行了 import
一樣。如果它成功解析,則解析後的 specifier 字串將用作模組快取中的 key。這表示您可以使用相對路徑、絕對路徑,甚至模組名稱。如果 specifier
無法解析,則原始的 specifier
將用作模組快取中的 key。
解析後,mock 模組會儲存在 ES Module 註冊表**和** CommonJS require 快取中。這表示您可以針對 mock 模組交換使用 import
和 require
。
回呼函式只有在模組被匯入或 require 時才會延遲呼叫。這表示您可以使用 mock.module()
來 mock 尚不存在的模組,並且表示您可以使用 mock.module()
來 mock 被其他模組匯入的模組。
使用 mock.restore()
將所有函式 mock 還原為其原始值
與其使用 mockFn.mockRestore()
手動個別還原每個 mock,不如透過呼叫 mock.restore()
以一個指令還原所有 mock。這樣做不會重設使用 mock.module()
覆寫的模組值。
在每個測試檔案的 afterEach
區塊中,甚至在您的 測試 preload 程式碼 中加入 mock.restore()
可以減少測試中的程式碼量。
import { expect, mock, spyOn, test } from "bun:test";
import * as fooModule from './foo.ts';
import * as barModule from './bar.ts';
import * as bazModule from './baz.ts';
test('foo, bar, baz', () => {
const fooSpy = spyOn(fooModule, 'foo');
const barSpy = spyOn(barModule, 'bar');
const bazSpy = spyOn(bazModule, 'baz');
expect(fooSpy).toBe('foo');
expect(barSpy).toBe('bar');
expect(bazSpy).toBe('baz');
fooSpy.mockImplementation(() => 42);
barSpy.mockImplementation(() => 43);
bazSpy.mockImplementation(() => 44);
expect(fooSpy).toBe(42);
expect(barSpy).toBe(43);
expect(bazSpy).toBe(44);
mock.restore();
expect(fooSpy).toBe('foo');
expect(barSpy).toBe('bar');
expect(bazSpy).toBe('baz');
});