Node.js v25.0.0 文件
- Node.js v25.0.0
-
目錄
- 測試執行器
- 子測試
- 跳過測試
- 重新執行失敗的測試
- TODO 測試
describe()和it()別名only測試- 按名稱篩選測試
- 無關的非同步活動
- 監視模式
- 全域性設定和拆卸
- 從命令列執行測試
- 收集程式碼覆蓋率
- 模擬 (Mocking)
- 快照測試
- 測試報告器
run([options])suite([name][, options][, fn])suite.skip([name][, options][, fn])suite.todo([name][, options][, fn])suite.only([name][, options][, fn])test([name][, options][, fn])test.skip([name][, options][, fn])test.todo([name][, options][, fn])test.only([name][, options][, fn])describe([name][, options][, fn])describe.skip([name][, options][, fn])describe.todo([name][, options][, fn])describe.only([name][, options][, fn])it([name][, options][, fn])it.skip([name][, options][, fn])it.todo([name][, options][, fn])it.only([name][, options][, fn])before([fn][, options])after([fn][, options])beforeEach([fn][, options])afterEach([fn][, options])assertsnapshot- 類:
MockFunctionContext - 類:
MockModuleContext - 類:
MockPropertyContext - 類:
MockTrackermock.fn([original[, implementation]][, options])mock.getter(object, methodName[, implementation][, options])mock.method(object, methodName[, implementation][, options])mock.module(specifier[, options])mock.property(object, propertyName[, value])mock.reset()mock.restoreAll()mock.setter(object, methodName[, implementation][, options])
- 類:
MockTimers - 類:
TestsStream - 類:
TestContextcontext.before([fn][, options])context.beforeEach([fn][, options])context.after([fn][, options])context.afterEach([fn][, options])context.assertcontext.diagnostic(message)context.filePathcontext.fullNamecontext.namecontext.plan(count[,options])context.runOnly(shouldRunOnlyTests)context.signalcontext.skip([message])context.todo([message])context.test([name][, options][, fn])context.waitFor(condition[, options])
- 類:
SuiteContext
- 測試執行器
-
索引
- 斷言測試
- 非同步上下文跟蹤
- 非同步鉤子
- 緩衝區
- C++ 外掛
- 使用 Node-API 的 C/C++ 外掛
- C++ 嵌入器 API
- 子程序
- 叢集
- 命令列選項
- 控制檯
- 加密
- 偵錯程式
- 已棄用的 API
- 診斷通道
- DNS
- 域
- 環境變數
- 錯誤
- 事件
- 檔案系統
- 全域性物件
- HTTP
- HTTP/2
- HTTPS
- 檢查器
- 國際化
- 模組:CommonJS 模組
- 模組:ECMAScript 模組
- 模組:
node:moduleAPI - 模組:包
- 模組:TypeScript
- 網路
- 作業系統
- 路徑
- 效能鉤子
- 許可權
- 程序
- Punycode
- 查詢字串
- 逐行讀取
- REPL
- 報告
- 單一可執行檔案應用
- SQLite
- 流
- 字串解碼器
- 測試執行器
- 定時器
- TLS/SSL
- 跟蹤事件
- TTY
- UDP/資料報
- URL
- 實用工具
- V8
- 虛擬機器
- WASI
- Web Crypto API
- Web Streams API
- 工作執行緒
- Zlib
- 其他版本
- 選項
測試執行器#
原始碼: lib/test.js
node:test 模組有助於建立 JavaScript 測試。要訪問它:
import test from 'node:test';const test = require('node:test');
此模組僅在 node: 協議下可用。
透過 test 模組建立的測試由一個函式組成,該函式透過以下三種方式之一進行處理:
- 一個同步函式,如果它丟擲異常則被視為失敗,否則被視為透過。
- 一個返回
Promise的函式,如果Promise被拒絕(reject),則被視為失敗,如果Promise被兌現(fulfill),則被視為透過。 - 一個接收回調函式的函式。如果回撥函式收到的第一個引數是任何真值(truthy value),則測試被視為失敗。如果傳遞給回撥函式的第一個引數是假值(falsy value),則測試被視為透過。如果測試函式接收了一個回撥函式並且還返回了一個
Promise,則測試將失敗。
以下示例說明了如何使用 test 模組編寫測試。
test('synchronous passing test', (t) => {
// This test passes because it does not throw an exception.
assert.strictEqual(1, 1);
});
test('synchronous failing test', (t) => {
// This test fails because it throws an exception.
assert.strictEqual(1, 2);
});
test('asynchronous passing test', async (t) => {
// This test passes because the Promise returned by the async
// function is settled and not rejected.
assert.strictEqual(1, 1);
});
test('asynchronous failing test', async (t) => {
// This test fails because the Promise returned by the async
// function is rejected.
assert.strictEqual(1, 2);
});
test('failing test using Promises', (t) => {
// Promises can be used directly as well.
return new Promise((resolve, reject) => {
setImmediate(() => {
reject(new Error('this will cause the test to fail'));
});
});
});
test('callback passing test', (t, done) => {
// done() is the callback function. When the setImmediate() runs, it invokes
// done() with no arguments.
setImmediate(done);
});
test('callback failing test', (t, done) => {
// When the setImmediate() runs, done() is invoked with an Error object and
// the test fails.
setImmediate(() => {
done(new Error('callback failure'));
});
});
如果有任何測試失敗,程序退出碼將被設定為 1。
子測試#
測試上下文的 test() 方法允許建立子測試。它允許您以分層方式組織測試,即可以在一個更大的測試中建立巢狀測試。此方法的行為與頂層 test() 函式完全相同。以下示例演示了建立一個帶有兩個子測試的頂層測試。
test('top level test', async (t) => {
await t.test('subtest 1', (t) => {
assert.strictEqual(1, 1);
});
await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2);
});
});
注意:
beforeEach和afterEach鉤子(hooks)會在每個子測試執行之間觸發。
在此示例中,使用了 await 來確保兩個子測試都已完成。這是必需的,因為測試不會等待其子測試完成,這與在測試套件(suite)中建立的測試不同。任何在其父測試完成時仍未完成的子測試都將被取消並視為失敗。任何子測試的失敗都會導致父測試失敗。
跳過測試#
可以透過向測試傳遞 skip 選項,或呼叫測試上下文的 skip() 方法來跳過單個測試,如下例所示。
// The skip option is used, but no message is provided.
test('skip option', { skip: true }, (t) => {
// This code is never executed.
});
// The skip option is used, and a message is provided.
test('skip option with message', { skip: 'this is skipped' }, (t) => {
// This code is never executed.
});
test('skip() method', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip();
});
test('skip() method with message', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip('this is skipped');
});
重新執行失敗的測試#
測試執行器支援將執行狀態持久化到檔案中,從而允許測試執行器重新執行失敗的測試,而無需重新執行整個測試套件。使用 --test-rerun-failures 命令列選項指定一個檔案路徑,用於儲存執行狀態。如果狀態檔案不存在,測試執行器將建立它。狀態檔案是一個 JSON 檔案,其中包含一個執行嘗試的陣列。每次執行嘗試都是一個物件,將成功的測試對映到它們透過的嘗試次數。在此對映中,標識測試的鍵是測試檔案路徑,以及測試定義的行號和列號。在特定位置定義的測試被多次執行的情況下(例如在函式或迴圈中),將在鍵的末尾附加一個計數器,以消除測試執行的歧義。請注意,更改測試執行順序或測試位置可能導致測試執行器認為測試已在先前的嘗試中透過,這意味著 --test-rerun-failures 應該在測試以確定性順序執行時使用。
狀態檔案示例:
[
{
"test.js:10:5": { "passed_on_attempt": 0, "name": "test 1" },
},
{
"test.js:10:5": { "passed_on_attempt": 0, "name": "test 1" },
"test.js:20:5": { "passed_on_attempt": 1, "name": "test 2" }
}
]
在此示例中,有兩次執行嘗試,在 test.js 中定義了兩個測試,第一個測試在第一次嘗試時成功,第二個測試在第二次嘗試時成功。
當使用 --test-rerun-failures 選項時,測試執行器將只執行尚未透過的測試。
node --test-rerun-failures /path/to/state/file
TODO 測試#
可以透過向測試傳遞 todo 選項或呼叫測試上下文的 todo() 方法,將單個測試標記為不穩定的(flaky)或未完成的,如下例所示。這些測試代表待實現的功能或需要修復的錯誤。TODO 測試會被執行,但不會被視為測試失敗,因此不會影響程序的退出碼。如果一個測試同時被標記為 TODO 和跳過(skipped),則 TODO 選項將被忽略。
// The todo option is used, but no message is provided.
test('todo option', { todo: true }, (t) => {
// This code is executed, but not treated as a failure.
throw new Error('this does not fail the test');
});
// The todo option is used, and a message is provided.
test('todo option with message', { todo: 'this is a todo test' }, (t) => {
// This code is executed.
});
test('todo() method', (t) => {
t.todo();
});
test('todo() method with message', (t) => {
t.todo('this is a todo test and is not treated as a failure');
throw new Error('this does not fail the test');
});
describe() 和 it() 別名#
測試套件和測試也可以使用 describe() 和 it() 函式來編寫。describe() 是 suite() 的別名,而 it() 是 test() 的別名。
describe('A thing', () => {
it('should work', () => {
assert.strictEqual(1, 1);
});
it('should be ok', () => {
assert.strictEqual(2, 2);
});
describe('a nested thing', () => {
it('should work', () => {
assert.strictEqual(3, 3);
});
});
});
describe() 和 it() 從 node:test 模組匯入。
import { describe, it } from 'node:test';const { describe, it } = require('node:test');
only 測試#
如果 Node.js 啟動時帶有 --test-only 命令列選項,或者測試隔離被停用,可以透過嚮應執行的測試傳遞 only 選項來跳過除選定子集之外的所有測試。當設定了帶有 only 選項的測試時,所有子測試也會執行。如果一個測試套件(suite)設定了 only 選項,則該套件內的所有測試都會執行,除非它有設定了 only 選項的後代,在這種情況下,只有那些測試會執行。
在 test()/it() 中使用子測試時,需要將所有祖先測試都標記為 only 選項,才能僅執行選定的測試子集。
測試上下文的 runOnly() 方法可用於在子測試級別實現相同的行為。未執行的測試將從測試執行器的輸出中省略。
// Assume Node.js is run with the --test-only command-line option.
// The suite's 'only' option is set, so these tests are run.
test('this test is run', { only: true }, async (t) => {
// Within this test, all subtests are run by default.
await t.test('running subtest');
// The test context can be updated to run subtests with the 'only' option.
t.runOnly(true);
await t.test('this subtest is now skipped');
await t.test('this subtest is run', { only: true });
// Switch the context back to execute all tests.
t.runOnly(false);
await t.test('this subtest is now run');
// Explicitly do not run these tests.
await t.test('skipped subtest 3', { only: false });
await t.test('skipped subtest 4', { skip: true });
});
// The 'only' option is not set, so this test is skipped.
test('this test is not run', () => {
// This code is not run.
throw new Error('fail');
});
describe('a suite', () => {
// The 'only' option is set, so this test is run.
it('this test is run', { only: true }, () => {
// This code is run.
});
it('this test is not run', () => {
// This code is not run.
throw new Error('fail');
});
});
describe.only('a suite', () => {
// The 'only' option is set, so this test is run.
it('this test is run', () => {
// This code is run.
});
it('this test is run', () => {
// This code is run.
});
});
按名稱篩選測試#
--test-name-pattern 命令列選項可用於僅執行名稱與所提供模式匹配的測試,而 --test-skip-pattern 選項可用於跳過名稱與所提供模式匹配的測試。測試名稱模式被解釋為 JavaScript 正則表示式。--test-name-pattern 和 --test-skip-pattern 選項可以多次指定,以便執行巢狀測試。對於每個執行的測試,任何相應的測試鉤子(如 beforeEach())也會執行。未執行的測試將從測試執行器的輸出中省略。
給定以下測試檔案,使用 --test-name-pattern="test [1-3]" 選項啟動 Node.js 將導致測試執行器執行 test 1、test 2 和 test 3。如果 test 1 不匹配測試名稱模式,那麼它的子測試即使匹配模式也不會執行。同一組測試也可以透過多次傳遞 --test-name-pattern 來執行(例如 --test-name-pattern="test 1"、--test-name-pattern="test 2" 等)。
test('test 1', async (t) => {
await t.test('test 2');
await t.test('test 3');
});
test('Test 4', async (t) => {
await t.test('Test 5');
await t.test('test 6');
});
測試名稱模式也可以使用正則表示式字面量來指定。這允許使用正則表示式標誌。在前面的示例中,使用 --test-name-pattern="/test [4-5]/i"(或 --test-skip-pattern="/test [4-5]/i")啟動 Node.js 將匹配 Test 4 和 Test 5,因為該模式是大小寫不敏感的。
要用模式匹配單個測試,您可以在其前面加上所有祖先測試的名稱,並用空格分隔,以確保其唯一性。例如,給定以下測試檔案:
describe('test 1', (t) => {
it('some test');
});
describe('test 2', (t) => {
it('some test');
});
使用 --test-name-pattern="test 1 some test" 啟動 Node.js 將只匹配 test 1 中的 some test。
測試名稱模式不會改變測試執行器執行的檔案集。
如果同時提供了 --test-name-pattern 和 --test-skip-pattern,測試必須滿足兩個要求才能被執行。
無關的非同步活動#
一旦測試函式執行完畢,結果會盡可能快地報告,同時保持測試的順序。然而,測試函式可能會產生比測試本身生命週期更長的非同步活動。測試執行器會處理這種型別的活動,但不會為了容納它而延遲報告測試結果。
在以下示例中,一個測試完成時還有兩個 setImmediate() 操作未完成。第一個 setImmediate() 嘗試建立一個新的子測試。因為父測試已經完成並輸出了其結果,所以新的子測試會立即被標記為失敗,並稍後報告給 <TestsStream>。
第二個 setImmediate() 建立了一個 uncaughtException 事件。源自已完成測試的 uncaughtException 和 unhandledRejection 事件會被 test 模組標記為失敗,並由 <TestsStream> 在頂層作為診斷警告報告。
test('a test that creates asynchronous activity', (t) => {
setImmediate(() => {
t.test('subtest that is created too late', (t) => {
throw new Error('error1');
});
});
setImmediate(() => {
throw new Error('error2');
});
// The test finishes after this line.
});
監視模式#
Node.js 測試執行器支援透過傳遞 --watch 標誌在監視模式下執行:
node --test --watch
在監視模式下,測試執行器將監視測試檔案及其依賴項的更改。當檢測到更改時,測試執行器將重新執行受更改影響的測試。測試執行器將持續執行,直到程序被終止。
全域性設定和拆卸#
測試執行器支援指定一個模組,該模組將在所有測試執行前被評估,可用於為測試設定全域性狀態或固定裝置(fixtures)。這對於準備資源或設定多個測試所需的共享狀態非常有用。
此模組可以匯出以下任何一項:
- 一個
globalSetup函式,它在所有測試開始前執行一次 - 一個
globalTeardown函式,它在所有測試完成後執行一次
在從命令列執行測試時,使用 --test-global-setup 標誌指定該模組。
// setup-module.js
async function globalSetup() {
// Setup shared resources, state, or environment
console.log('Global setup executed');
// Run servers, create files, prepare databases, etc.
}
async function globalTeardown() {
// Clean up resources, state, or environment
console.log('Global teardown executed');
// Close servers, remove files, disconnect from databases, etc.
}
module.exports = { globalSetup, globalTeardown };// setup-module.mjs
export async function globalSetup() {
// Setup shared resources, state, or environment
console.log('Global setup executed');
// Run servers, create files, prepare databases, etc.
}
export async function globalTeardown() {
// Clean up resources, state, or environment
console.log('Global teardown executed');
// Close servers, remove files, disconnect from databases, etc.
}
如果全域性設定函式丟擲錯誤,則不會執行任何測試,並且程序將以非零退出碼退出。在這種情況下,不會呼叫全域性拆卸函式。
從命令列執行測試#
可以透過傳遞 --test 標誌從命令列呼叫 Node.js 測試執行器:
node --test
預設情況下,Node.js 將執行所有匹配這些模式的檔案:
**/*.test.{cjs,mjs,js}**/*-test.{cjs,mjs,js}**/*_test.{cjs,mjs,js}**/test-*.{cjs,mjs,js}**/test.{cjs,mjs,js}**/test/**/*.{cjs,mjs,js}
除非提供了 --no-experimental-strip-types,否則還會匹配以下附加模式:
**/*.test.{cts,mts,ts}**/*-test.{cts,mts,ts}**/*_test.{cts,mts,ts}**/test-*.{cts,mts,ts}**/test.{cts,mts,ts}**/test/**/*.{cts,mts,ts}
或者,可以將一個或多個 glob 模式作為 Node.js 命令的最後引數提供,如下所示。Glob 模式遵循 glob(7) 的行為。Glob 模式應在命令列上用雙引號括起來,以防止 shell 擴充套件,這會降低跨系統的可移植性。
node --test "**/*.test.js" "**/*.spec.js"
匹配的檔案將作為測試檔案執行。有關測試檔案執行的更多資訊,請參見測試執行器執行模型部分。
測試執行器執行模型#
當啟用程序級測試隔離時,每個匹配的測試檔案都在一個單獨的子程序中執行。任何時候執行的最大子程序數由 --test-concurrency 標誌控制。如果子程序以退出碼 0 結束,則測試被視為透過。否則,測試被視為失敗。測試檔案必須可由 Node.js 執行,但不需要在內部使用 node:test 模組。
每個測試檔案都像常規指令碼一樣執行。也就是說,如果測試檔案本身使用 node:test 來定義測試,所有這些測試都將在單個應用程式執行緒中執行,而不管 test() 的 concurrency 選項的值如何。
當停用程序級測試隔離時,每個匹配的測試檔案都被匯入到測試執行器程序中。一旦所有測試檔案載入完畢,頂層測試將以併發數為一的方式執行。由於所有測試檔案都在同一上下文中執行,測試之間可能會以啟用隔離時不可能的方式相互作用。例如,如果一個測試依賴於全域性狀態,該狀態可能會被來自另一個檔案的測試修改。
子程序選項繼承#
在程序隔離模式(預設)下執行測試時,生成的子程序會從父程序繼承 Node.js 選項,包括在配置檔案中指定的選項。但是,某些標誌會被過濾掉以確保測試執行器的正常功能:
--test- 阻止以避免遞迴測試執行--experimental-test-coverage- 由測試執行器管理--watch- 監視模式在父級處理--experimental-default-config-file- 配置檔案載入由父級處理--test-reporter- 報告由父程序管理--test-reporter-destination- 輸出目標由父級控制--experimental-config-file- 配置檔案路徑由父級管理
來自命令列引數、環境變數和配置檔案的所有其他 Node.js 選項都由子程序繼承。
收集程式碼覆蓋率#
當 Node.js 啟動時帶有 --experimental-test-coverage 命令列標誌時,會收集程式碼覆蓋率,並在所有測試完成後報告統計資訊。如果使用 NODE_V8_COVERAGE 環境變數指定程式碼覆蓋率目錄,則生成的 V8 覆蓋率檔案將寫入該目錄。Node.js 核心模組和 node_modules/ 目錄中的檔案預設不包含在覆蓋率報告中。但是,可以透過 --test-coverage-include 標誌明確包含它們。預設情況下,所有匹配的測試檔案都從覆蓋率報告中排除。可以使用 --test-coverage-exclude 標誌覆蓋排除項。如果啟用了覆蓋率,覆蓋率報告將透過 'test:coverage' 事件傳送給任何測試報告器。
可以使用以下注釋語法在一系列行上停用覆蓋率:
/* node:coverage disable */
if (anAlwaysFalseCondition) {
// Code in this branch will never be executed, but the lines are ignored for
// coverage purposes. All lines following the 'disable' comment are ignored
// until a corresponding 'enable' comment is encountered.
console.log('this is never executed');
}
/* node:coverage enable */
也可以為指定數量的行停用覆蓋率。在指定行數之後,覆蓋率將自動重新啟用。如果未明確提供行數,則忽略單行。
/* node:coverage ignore next */
if (anAlwaysFalseCondition) { console.log('this is never executed'); }
/* node:coverage ignore next 3 */
if (anAlwaysFalseCondition) {
console.log('this is never executed');
}
覆蓋率報告器#
tap 和 spec 報告器將列印覆蓋率統計摘要。還有一個 lcov 報告器,它將生成一個 lcov 檔案,可用作深入的覆蓋率報告。
node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info
- 此報告器不報告任何測試結果。
- 此報告器最好與另一個報告器一起使用。
模擬 (Mocking)#
node:test 模組支援在測試期間透過頂層 mock 物件進行模擬。以下示例為一個將兩個數字相加的函式建立一個偵察器(spy)。然後使用該偵察器來斷言函式是否按預期被呼叫。
import assert from 'node:assert';
import { mock, test } from 'node:test';
test('spies on a function', () => {
const sum = mock.fn((a, b) => {
return a + b;
});
assert.strictEqual(sum.mock.callCount(), 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.callCount(), 1);
const call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, 7);
assert.strictEqual(call.error, undefined);
// Reset the globally tracked mocks.
mock.reset();
});'use strict';
const assert = require('node:assert');
const { mock, test } = require('node:test');
test('spies on a function', () => {
const sum = mock.fn((a, b) => {
return a + b;
});
assert.strictEqual(sum.mock.callCount(), 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.callCount(), 1);
const call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, 7);
assert.strictEqual(call.error, undefined);
// Reset the globally tracked mocks.
mock.reset();
});
同樣的模擬功能也暴露在每個測試的 TestContext 物件上。以下示例使用 TestContext 上暴露的 API 為一個物件方法建立一個偵察器。透過測試上下文進行模擬的好處是,測試執行器將在測試完成後自動恢復所有被模擬的功能。
test('spies on an object method', (t) => {
const number = {
value: 5,
add(a) {
return this.value + a;
},
};
t.mock.method(number, 'add');
assert.strictEqual(number.add.mock.callCount(), 0);
assert.strictEqual(number.add(3), 8);
assert.strictEqual(number.add.mock.callCount(), 1);
const call = number.add.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3]);
assert.strictEqual(call.result, 8);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, number);
});
定時器#
模擬定時器是軟體測試中常用的一種技術,用於模擬和控制定時器(如 setInterval 和 setTimeout)的行為,而無需實際等待指定的時間間隔。
有關方法和功能的完整列表,請參閱 MockTimers 類。
這使得開發者能夠為依賴時間的功能編寫更可靠、更可預測的測試。
下面的例子展示瞭如何模擬 setTimeout。使用 .enable({ apis: ['setTimeout'] }); 將會模擬 node:timers 和 node:timers/promises 模組以及 Node.js 全域性上下文中的 setTimeout 函式。
注意: 此 API 目前不支援解構函式,例如 import { setTimeout } from 'node:timers'。
import assert from 'node:assert';
import { mock, test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', () => {
const fn = mock.fn();
// Optionally choose what to mock
mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
// Reset the globally tracked mocks.
mock.timers.reset();
// If you call reset mock instance, it will also reset timers instance
mock.reset();
});const assert = require('node:assert');
const { mock, test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', () => {
const fn = mock.fn();
// Optionally choose what to mock
mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
// Reset the globally tracked mocks.
mock.timers.reset();
// If you call reset mock instance, it will also reset timers instance
mock.reset();
});
同樣的模擬功能也暴露在每個測試的 TestContext 物件的 mock 屬性中。透過測試上下文進行模擬的好處是,測試執行器將在測試完成後自動恢復所有被模擬的定時器功能。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
日期#
模擬定時器 API 還允許模擬 Date 物件。這是一個有用的功能,用於測試依賴時間的功能,或模擬內部日曆函式,如 Date.now()。
日期的實現也是 MockTimers 類的一部分。有關方法和功能的完整列表,請參閱該類。
注意: 當日期和定時器一起被模擬時,它們是相互依賴的。這意味著,如果您同時模擬了 Date 和 setTimeout,推進時間也會推進模擬的日期,因為它們模擬了一個單一的內部時鐘。
下面的例子展示瞭如何模擬 Date 物件並獲取當前的 Date.now() 值。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks the Date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'] });
// If not specified, the initial date will be based on 0 in the UNIX epoch
assert.strictEqual(Date.now(), 0);
// Advance in time will also advance the date
context.mock.timers.tick(9999);
assert.strictEqual(Date.now(), 9999);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks the Date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'] });
// If not specified, the initial date will be based on 0 in the UNIX epoch
assert.strictEqual(Date.now(), 0);
// Advance in time will also advance the date
context.mock.timers.tick(9999);
assert.strictEqual(Date.now(), 9999);
});
如果沒有設定初始的紀元時間,初始日期將基於 Unix 紀元中的 0。即 1970 年 1 月 1 日 00:00:00 UTC。您可以透過向 .enable() 方法傳遞一個 now 屬性來設定一個初始日期。該值將用作模擬 Date 物件的初始日期。它可以是一個正整數,也可以是另一個 Date 物件。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks the Date object with initial time', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 300);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks the Date object with initial time', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 300);
});
您可以使用 .setTime() 方法手動將模擬的日期移動到另一個時間。此方法只接受一個正整數。
注意: 此方法將執行所有在新時間之前應該觸發的模擬定時器。
在下面的示例中,我們為模擬日期設定了一個新的時間。
import assert from 'node:assert';
import { test } from 'node:test';
test('sets the time of a date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.setTime(1000);
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 1200);
});const assert = require('node:assert');
const { test } = require('node:test');
test('sets the time of a date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.setTime(1000);
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 1200);
});
如果您有任何設定為在過去執行的定時器,它將被執行,就好像呼叫了 .tick() 方法一樣。如果您想測試已經過去的依賴時間的功能,這將非常有用。
import assert from 'node:assert';
import { test } from 'node:test';
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
context.mock.timers.setTime(800);
// Timer is not executed as the time is not yet reached
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 800);
context.mock.timers.setTime(1200);
// Timer is executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 1200);
});const assert = require('node:assert');
const { test } = require('node:test');
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
context.mock.timers.setTime(800);
// Timer is not executed as the time is not yet reached
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 800);
context.mock.timers.setTime(1200);
// Timer is executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 1200);
});
使用 .runAll() 將執行當前佇列中的所有定時器。這也會將模擬的日期推進到最後一個被執行的定時器的時間,就好像時間已經流逝了一樣。
import assert from 'node:assert';
import { test } from 'node:test';
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
setTimeout(fn, 2000);
setTimeout(fn, 3000);
context.mock.timers.runAll();
// All timers are executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 3);
assert.strictEqual(Date.now(), 3000);
});const assert = require('node:assert');
const { test } = require('node:test');
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
setTimeout(fn, 2000);
setTimeout(fn, 3000);
context.mock.timers.runAll();
// All timers are executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 3);
assert.strictEqual(Date.now(), 3000);
});
快照測試#
快照測試允許將任意值序列化為字串值,並與一組已知良好值進行比較。已知良好值被稱為快照,並存儲在快照檔案中。快照檔案由測試執行器管理,但設計為人類可讀,以幫助除錯。最佳實踐是將快照檔案與測試檔案一起檢入原始碼控制。
快照檔案透過使用 --test-update-snapshots 命令列標誌啟動 Node.js 來生成。為每個測試檔案生成一個單獨的快照檔案。預設情況下,快照檔案的名稱與測試檔案相同,但副檔名為 .snapshot。可以使用 snapshot.setResolveSnapshotPath() 函式配置此行為。每個快照斷言對應於快照檔案中的一個匯出項。
下面顯示了一個快照測試的示例。第一次執行此測試時,它會失敗,因為相應的快照檔案不存在。
// test.js
suite('suite of snapshot tests', () => {
test('snapshot test', (t) => {
t.assert.snapshot({ value1: 1, value2: 2 });
t.assert.snapshot(5);
});
});
透過使用 --test-update-snapshots 執行測試檔案來生成快照檔案。測試應該透過,並在與測試檔案相同的目錄中建立一個名為 test.js.snapshot 的檔案。快照檔案的內容如下所示。每個快照都由測試的全名和一個計數器來標識,以區分同一測試中的快照。
exports[`suite of snapshot tests > snapshot test 1`] = `
{
"value1": 1,
"value2": 2
}
`;
exports[`suite of snapshot tests > snapshot test 2`] = `
5
`;
快照檔案建立後,再次執行測試,但不要帶 --test-update-snapshots 標誌。現在測試應該會透過。
測試報告器#
node:test 模組支援傳遞 --test-reporter 標誌,以使測試執行器使用特定的報告器。
支援以下內建報告器:
-
specspec報告器以人類可讀的格式輸出測試結果。這是預設報告器。 -
taptap報告器以 TAP 格式輸出測試結果。 -
dotdot報告器以緊湊格式輸出測試結果,其中每個透過的測試由一個.表示,每個失敗的測試由一個X表示。 -
junitjunit 報告器以 jUnit XML 格式輸出測試結果。 -
lcov當與--experimental-test-coverage標誌一起使用時,lcov報告器輸出測試覆蓋率。
這些報告器的確切輸出可能會在 Node.js 版本之間發生變化,不應在程式中依賴它。如果需要以程式設計方式訪問測試執行器的輸出,請使用由 <TestsStream> 發出的事件。
報告器可透過 node:test/reporters 模組獲得。
import { tap, spec, dot, junit, lcov } from 'node:test/reporters';const { tap, spec, dot, junit, lcov } = require('node:test/reporters');
自定義報告器#
--test-reporter 可用於指定自定義報告器的路徑。自定義報告器是一個模組,它匯出一個被 stream.compose 接受的值。報告器應轉換由 <TestsStream> 發出的事件。
使用 <stream.Transform> 的自定義報告器示例:
import { Transform } from 'node:stream';
const customReporter = new Transform({
writableObjectMode: true,
transform(event, encoding, callback) {
switch (event.type) {
case 'test:dequeue':
callback(null, `test ${event.data.name} dequeued`);
break;
case 'test:enqueue':
callback(null, `test ${event.data.name} enqueued`);
break;
case 'test:watch:drained':
callback(null, 'test watch queue drained');
break;
case 'test:watch:restarted':
callback(null, 'test watch restarted due to file change');
break;
case 'test:start':
callback(null, `test ${event.data.name} started`);
break;
case 'test:pass':
callback(null, `test ${event.data.name} passed`);
break;
case 'test:fail':
callback(null, `test ${event.data.name} failed`);
break;
case 'test:plan':
callback(null, 'test plan');
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
callback(null, event.data.message);
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
callback(null, `total line count: ${totalLineCount}\n`);
break;
}
}
},
});
export default customReporter;const { Transform } = require('node:stream');
const customReporter = new Transform({
writableObjectMode: true,
transform(event, encoding, callback) {
switch (event.type) {
case 'test:dequeue':
callback(null, `test ${event.data.name} dequeued`);
break;
case 'test:enqueue':
callback(null, `test ${event.data.name} enqueued`);
break;
case 'test:watch:drained':
callback(null, 'test watch queue drained');
break;
case 'test:watch:restarted':
callback(null, 'test watch restarted due to file change');
break;
case 'test:start':
callback(null, `test ${event.data.name} started`);
break;
case 'test:pass':
callback(null, `test ${event.data.name} passed`);
break;
case 'test:fail':
callback(null, `test ${event.data.name} failed`);
break;
case 'test:plan':
callback(null, 'test plan');
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
callback(null, event.data.message);
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
callback(null, `total line count: ${totalLineCount}\n`);
break;
}
}
},
});
module.exports = customReporter;
使用生成器函式的自定義報告器示例:
export default async function * customReporter(source) {
for await (const event of source) {
switch (event.type) {
case 'test:dequeue':
yield `test ${event.data.name} dequeued\n`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued\n`;
break;
case 'test:watch:drained':
yield 'test watch queue drained\n';
break;
case 'test:watch:restarted':
yield 'test watch restarted due to file change\n';
break;
case 'test:start':
yield `test ${event.data.name} started\n`;
break;
case 'test:pass':
yield `test ${event.data.name} passed\n`;
break;
case 'test:fail':
yield `test ${event.data.name} failed\n`;
break;
case 'test:plan':
yield 'test plan\n';
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
yield `${event.data.message}\n`;
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
yield `total line count: ${totalLineCount}\n`;
break;
}
}
}
}module.exports = async function * customReporter(source) {
for await (const event of source) {
switch (event.type) {
case 'test:dequeue':
yield `test ${event.data.name} dequeued\n`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued\n`;
break;
case 'test:watch:drained':
yield 'test watch queue drained\n';
break;
case 'test:watch:restarted':
yield 'test watch restarted due to file change\n';
break;
case 'test:start':
yield `test ${event.data.name} started\n`;
break;
case 'test:pass':
yield `test ${event.data.name} passed\n`;
break;
case 'test:fail':
yield `test ${event.data.name} failed\n`;
break;
case 'test:plan':
yield 'test plan\n';
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
yield `${event.data.message}\n`;
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
yield `total line count: ${totalLineCount}\n`;
break;
}
}
}
};
提供給 --test-reporter 的值應該是一個字串,就像在 JavaScript 程式碼的 import() 中使用的那樣,或者是為 --import 提供的值。
多個報告器#
--test-reporter 標誌可以多次指定,以多種格式報告測試結果。在這種情況下,需要使用 --test-reporter-destination 為每個報告器指定一個目標。目標可以是 stdout、stderr 或檔案路徑。報告器和目標根據它們指定的順序進行配對。
在以下示例中,spec 報告器將輸出到 stdout,而 dot 報告器將輸出到 file.txt:
node --test-reporter=spec --test-reporter=dot --test-reporter-destination=stdout --test-reporter-destination=file.txt
當指定單個報告器時,目標將預設為 stdout,除非明確提供了目標。
run([options])#
options<Object> 執行測試的配置選項。支援以下屬性:concurrency<number> | <boolean> 如果提供一個數字,則該數量的測試程序將並行執行,每個程序對應一個測試檔案。如果為true,它將並行執行os.availableParallelism() - 1個測試檔案。如果為false,它將一次只執行一個測試檔案。預設值:false。cwd<string> 指定測試執行器要使用的當前工作目錄。作為解析檔案的基本路徑,就像從該目錄從命令列執行測試一樣。預設值:process.cwd()。files<Array> 一個包含要執行的檔案列表的陣列。預設值: 與從命令列執行測試相同。forceExit<boolean> 配置測試執行器在所有已知測試執行完畢後退出程序,即使事件迴圈原本會保持活動狀態。預設值:false。globPatterns<Array> 一個包含用於匹配測試檔案的 glob 模式列表的陣列。此選項不能與files一起使用。預設值: 與從命令列執行測試相同。inspectPort<number> | <Function> 設定測試子程序的檢查器埠。這可以是一個數字,或一個不帶引數並返回數字的函式。如果提供了一個空值(nullish value),每個程序將獲得自己的埠,從主程序的process.debugPort開始遞增。如果isolation選項設定為'none',則此選項被忽略,因為不會生成子程序。預設值:undefined。isolation<string> 配置測試隔離的型別。如果設定為'process',每個測試檔案都在一個單獨的子程序中執行。如果設定為'none',所有測試檔案都在當前程序中執行。預設值:'process'。only<boolean> 如果為真值,測試上下文將只執行設定了only選項的測試。setup<Function> 一個接受TestsStream例項的函式,可用於在任何測試執行之前設定監聽器。預設值:undefined。execArgv<Array> 一個在生成子程序時傳遞給node可執行檔案的 CLI 標誌陣列。當isolation為'none'時,此選項無效。預設值:[]argv<Array> 一個在生成子程序時傳遞給每個測試檔案的 CLI 標誌陣列。當isolation為'none'時,此選項無效。預設值:[]。signal<AbortSignal> 允許中止正在進行的測試執行。testNamePatterns<string> | <RegExp> | <Array> 一個字串、RegExp 或 RegExp 陣列,可用於僅執行名稱與所提供模式匹配的測試。測試名稱模式被解釋為 JavaScript 正則表示式。對於每個執行的測試,任何相應的測試鉤子,如beforeEach(),也會執行。預設值:undefined。testSkipPatterns<string> | <RegExp> | <Array> 一個字串、RegExp 或 RegExp 陣列,可用於排除執行名稱與所提供模式匹配的測試。測試名稱模式被解釋為 JavaScript 正則表示式。對於每個執行的測試,任何相應的測試鉤子,如beforeEach(),也會執行。預設值:undefined。timeout<number> 測試執行將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。watch<boolean> 是否以監視模式執行。預設值:false。shard<Object> 在特定分片中執行測試。預設值:undefined。rerunFailuresFilePath<string> 一個檔案路徑,測試執行器將在其中儲存測試狀態,以便在下次執行時僅重新執行失敗的測試。有關更多資訊,請參閱[重新執行失敗的測試][]。預設值:undefined。coverage<boolean> 啟用程式碼覆蓋率收集。預設值:false。coverageExcludeGlobs<string> | <Array> 使用 glob 模式從程式碼覆蓋率中排除特定檔案,該模式可以匹配絕對和相對檔案路徑。此屬性僅在coverage設定為true時適用。如果同時提供了coverageExcludeGlobs和coverageIncludeGlobs,檔案必須滿足兩個條件才能被包含在覆蓋率報告中。預設值:undefined。coverageIncludeGlobs<string> | <Array> 使用 glob 模式在程式碼覆蓋率中包含特定檔案,該模式可以匹配絕對和相對檔案路徑。此屬性僅在coverage設定為true時適用。如果同時提供了coverageExcludeGlobs和coverageIncludeGlobs,檔案必須滿足兩個條件才能被包含在覆蓋率報告中。預設值:undefined。lineCoverage<number> 要求覆蓋行的最低百分比。如果程式碼覆蓋率未達到指定的閾值,程序將以程式碼1退出。預設值:0。branchCoverage<number> 要求覆蓋分支的最低百分比。如果程式碼覆蓋率未達到指定的閾值,程序將以程式碼1退出。預設值:0。functionCoverage<number> 要求覆蓋函式的最低百分比。如果程式碼覆蓋率未達到指定的閾值,程序將以程式碼1退出。預設值:0。
- 返回:<TestsStream>
注意: shard 用於在多臺機器或多個程序之間水平並行化測試執行,非常適合在不同環境中進行大規模執行。它與 watch 模式不相容,後者專為快速程式碼迭代而設計,透過在檔案更改時自動重新執行測試。
import { tap } from 'node:test/reporters';
import { run } from 'node:test';
import process from 'node:process';
import path from 'node:path';
run({ files: [path.resolve('./tests/test.js')] })
.on('test:fail', () => {
process.exitCode = 1;
})
.compose(tap)
.pipe(process.stdout);const { tap } = require('node:test/reporters');
const { run } = require('node:test');
const path = require('node:path');
run({ files: [path.resolve('./tests/test.js')] })
.on('test:fail', () => {
process.exitCode = 1;
})
.compose(tap)
.pipe(process.stdout);
suite([name][, options][, fn])#
name<string> 測試套件的名稱,在報告測試結果時顯示。預設值:fn的name屬性,如果fn沒有名稱,則為'<anonymous>'。options<Object> 測試套件的可選配置選項。它支援與test([name][, options][, fn])相同的選項。fn<Function> | <AsyncFunction> 宣告巢狀測試和套件的套件函式。此函式的第一個引數是SuiteContext物件。預設值: 一個空操作函式。- 返回:<Promise> 立即以
undefined兌現。
suite() 函式從 node:test 模組匯入。
suite.skip([name][, options][, fn])#
跳過測試套件的簡寫。這與 suite([name], { skip: true }[, fn]) 相同。
suite.todo([name][, options][, fn])#
將測試套件標記為 TODO 的簡寫。這與 suite([name], { todo: true }[, fn]) 相同。
suite.only([name][, options][, fn])#
將測試套件標記為 only 的簡寫。這與 suite([name], { only: true }[, fn]) 相同。
test([name][, options][, fn])#
name<string> 測試的名稱,在報告測試結果時顯示。預設值:fn的name屬性,如果fn沒有名稱,則為'<anonymous>'。options<Object> 測試的配置選項。支援以下屬性:concurrency<number> | <boolean> 如果提供一個數字,則該數量的測試將在應用程式執行緒內並行執行。如果為true,所有計劃的非同步測試將線上程內併發執行。如果為false,一次只執行一個測試。如果未指定,子測試將從其父級繼承此值。預設值:false。only<boolean> 如果為真值,並且測試上下文配置為只執行only測試,則此測試將被執行。否則,該測試將被跳過。預設值:false。signal<AbortSignal> 允許中止正在進行的測試。skip<boolean> | <string> 如果為真值,則跳過該測試。如果提供一個字串,該字串將作為跳過測試的原因顯示在測試結果中。預設值:false。todo<boolean> | <string> 如果為真值,則將測試標記為TODO。如果提供一個字串,該字串將作為測試為TODO的原因顯示在測試結果中。預設值:false。timeout<number> 測試將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。plan<number> 預計在測試中執行的斷言和子測試的數量。如果測試中執行的斷言數量與計劃中指定的數量不匹配,測試將失敗。預設值:undefined。
fn<Function> | <AsyncFunction> 被測試的函式。此函式的第一個引數是TestContext物件。如果測試使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。- 返回:<Promise> 一旦測試完成,將以
undefined兌現,或者如果測試在套件內執行,則立即兌現。
test() 函式是從 test 模組匯入的值。每次呼叫此函式都會導致向 <TestsStream> 報告測試。
傳遞給 fn 引數的 TestContext 物件可用於執行與當前測試相關的操作。例如,跳過測試、新增額外的診斷資訊或建立子測試。
test() 返回一個在測試完成後兌現的 Promise。如果 test() 在套件內呼叫,它會立即兌現。對於頂層測試,返回值通常可以被丟棄。然而,應使用子測試的返回值來防止父測試先完成並取消子測試,如下例所示。
test('top level test', async (t) => {
// The setTimeout() in the following subtest would cause it to outlive its
// parent test if 'await' is removed on the next line. Once the parent test
// completes, it will cancel any outstanding subtests.
await t.test('longer running subtest', async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});
timeout 選項可用於在測試完成時間超過 timeout 毫秒時使測試失敗。然而,它不是一個可靠的取消測試機制,因為一個正在執行的測試可能會阻塞應用程式執行緒,從而阻止預定的取消操作。
test.skip([name][, options][, fn])#
跳過測試的簡寫,與 test([name], { skip: true }[, fn]) 相同。
test.todo([name][, options][, fn])#
將測試標記為 TODO 的簡寫,與 test([name], { todo: true }[, fn]) 相同。
test.only([name][, options][, fn])#
將測試標記為 only 的簡寫,與 test([name], { only: true }[, fn]) 相同。
describe([name][, options][, fn])#
suite() 的別名。
describe() 函式從 node:test 模組匯入。
describe.skip([name][, options][, fn])#
跳過測試套件的簡寫。這與 describe([name], { skip: true }[, fn]) 相同。
describe.todo([name][, options][, fn])#
將測試套件標記為 TODO 的簡寫。這與 describe([name], { todo: true }[, fn]) 相同。
describe.only([name][, options][, fn])#
將測試套件標記為 only 的簡寫。這與 describe([name], { only: true }[, fn]) 相同。
it([name][, options][, fn])#
test() 的別名。
it() 函式從 node:test 模組匯入。
it.skip([name][, options][, fn])#
跳過測試的簡寫,與 it([name], { skip: true }[, fn]) 相同。
it.todo([name][, options][, fn])#
將測試標記為 TODO 的簡寫,與 it([name], { todo: true }[, fn]) 相同。
it.only([name][, options][, fn])#
將測試標記為 only 的簡寫,與 it([name], { only: true }[, fn]) 相同。
before([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。如果鉤子使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式建立一個在執行套件之前執行的鉤子。
describe('tests', async () => {
before(() => console.log('about to run some test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
after([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。如果鉤子使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式建立一個在執行套件之後執行的鉤子。
describe('tests', async () => {
after(() => console.log('finished running tests'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
注意: after 鉤子保證會執行,即使套件內的測試失敗。
beforeEach([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。如果鉤子使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式建立一個在當前套件中每個測試之前執行的鉤子。
describe('tests', async () => {
beforeEach(() => console.log('about to run a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
afterEach([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。如果鉤子使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式建立一個在當前套件中每個測試之後執行的鉤子。即使測試失敗,afterEach() 鉤子也會執行。
describe('tests', async () => {
afterEach(() => console.log('finished running a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
assert#
一個物件,其方法用於在當前程序中配置 TestContext 物件上可用的斷言。預設情況下,來自 node:assert 的方法和快照測試函式是可用的。
透過將通用配置程式碼放在使用 --require 或 --import 預載入的模組中,可以將相同的配置應用於所有檔案。
assert.register(name, fn)#
使用提供的名稱和函式定義一個新的斷言函式。如果已存在同名的斷言,它將被覆蓋。
snapshot#
一個物件,其方法用於在當前程序中配置預設的快照設定。透過將通用配置程式碼放在使用 --require 或 --import 預載入的模組中,可以將相同的配置應用於所有檔案。
snapshot.setDefaultSnapshotSerializers(serializers)#
serializers<Array> 一個同步函式陣列,用作快照測試的預設序列化器。
此函式用於自定義測試執行器使用的預設序列化機制。預設情況下,測試執行器透過在提供的值上呼叫 JSON.stringify(value, null, 2) 來執行序列化。JSON.stringify() 在處理迴圈結構和支援的資料型別方面存在限制。如果需要更強大的序列化機制,應使用此函式。
snapshot.setResolveSnapshotPath(fn)#
fn<Function> 一個用於計算快照檔案位置的函式。該函式接收測試檔案的路徑作為其唯一引數。如果測試與檔案無關(例如在 REPL 中),則輸入為 undefined。fn()必須返回一個指定快照檔案位置的字串。
此函式用於自定義快照測試所使用的快照檔案的位置。預設情況下,快照檔名與入口點檔名相同,但副檔名為 .snapshot。
類:MockFunctionContext#
MockFunctionContext 類用於檢查或操作透過 MockTracker API 建立的模擬(mocks)的行為。
ctx.calls#
- 型別:<Array>
一個 getter,返回用於跟蹤對模擬函式呼叫的內部陣列的副本。陣列中的每個條目都是一個具有以下屬性的物件。
arguments<Array> 一個包含傳遞給模擬函式的引數的陣列。error<any> 如果被模擬的函式丟擲異常,則此屬性包含丟擲的值。預設值:undefined。result<any> 被模擬函式返回的值。stack<Error> 一個Error物件,其堆疊可用於確定被模擬函式呼叫的位置。target<Function> | <undefined> 如果被模擬的函式是建構函式,此欄位包含正在構造的類。否則此欄位為undefined。this<any> 被模擬函式的this值。
ctx.callCount()#
- 返回:<integer> 此模擬被呼叫的次數。
此函式返回此模擬被呼叫的次數。此函式比檢查 ctx.calls.length 更高效,因為 ctx.calls 是一個建立內部呼叫跟蹤陣列副本的 getter。
ctx.mockImplementation(implementation)#
implementation<Function> | <AsyncFunction> 用作模擬新實現的函式。
此函式用於更改現有模擬的行為。
以下示例使用 t.mock.fn() 建立一個模擬函式,呼叫該模擬函式,然後將模擬實現更改為另一個函式。
test('changes a mock behavior', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne);
assert.strictEqual(fn(), 1);
fn.mock.mockImplementation(addTwo);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn(), 5);
});
ctx.mockImplementationOnce(implementation[, onCall])#
implementation<Function> | <AsyncFunction> 用於在onCall指定的呼叫次數時作為模擬實現的函式。onCall<integer> 將使用implementation的呼叫次數。如果指定的呼叫已經發生,則丟擲異常。預設值: 下一次呼叫的次數。
此函式用於在單次呼叫中更改現有模擬的行為。一旦第 onCall 次呼叫發生,模擬將恢復到未呼叫 mockImplementationOnce() 時的行為。
以下示例使用 t.mock.fn() 建立一個模擬函式,呼叫該模擬函式,在下一次呼叫時將模擬實現更改為另一個函式,然後恢復其先前的行為。
test('changes a mock behavior once', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne);
assert.strictEqual(fn(), 1);
fn.mock.mockImplementationOnce(addTwo);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn(), 4);
});
ctx.resetCalls()#
重置模擬函式的呼叫歷史記錄。
ctx.restore()#
將模擬函式的實現重置為其原始行為。呼叫此函式後,模擬仍然可以使用。
類:MockModuleContext#
MockModuleContext 類用於操作透過 MockTracker API 建立的模組模擬的行為。
ctx.restore()#
重置模擬模組的實現。
類:MockPropertyContext#
MockPropertyContext 類用於檢查或操作透過 MockTracker API 建立的屬性模擬的行為。
ctx.accessCount()#
- 返回:<integer> 屬性被訪問(讀取或寫入)的次數。
此函式返回屬性被訪問的次數。此函式比檢查 ctx.accesses.length 更高效,因為 ctx.accesses 是一個建立內部訪問跟蹤陣列副本的 getter。
ctx.mockImplementationOnce(value[, onAccess])#
value<any> 用於在由onAccess指定的呼叫次數時作為模擬實現的值。onAccess<integer> 將使用value的呼叫次數。如果指定的呼叫已經發生,則會丟擲異常。預設值: 下一次呼叫的次數。
此函式用於在單次呼叫中更改現有模擬的行為。一旦第 onAccess 次呼叫發生,模擬將恢復到未呼叫 mockImplementationOnce() 時的行為。
以下示例使用 t.mock.property() 建立一個模擬函式,呼叫該模擬屬性,在下一次呼叫時將模擬實現更改為不同的值,然後恢復其先前的行為。
test('changes a mock behavior once', (t) => {
const obj = { foo: 1 };
const prop = t.mock.property(obj, 'foo', 5);
assert.strictEqual(obj.foo, 5);
prop.mock.mockImplementationOnce(25);
assert.strictEqual(obj.foo, 25);
assert.strictEqual(obj.foo, 5);
});
注意事項#
為了與模擬 API 的其餘部分保持一致,此函式將屬性的 get 和 set 都視為訪問。如果屬性 set 發生在相同的訪問索引上,則“一次性”值將被 set 操作消耗,並且模擬屬性值將更改為“一次性”值。如果您希望“一次性”值僅用於 get 操作,這可能會導致意外行為。
ctx.resetAccesses()#
重置模擬屬性的訪問歷史記錄。
ctx.restore()#
將模擬屬性的實現重置為其原始行為。呼叫此函式後,模擬仍然可以使用。
類:MockTracker#
MockTracker 類用於管理模擬功能。測試執行器模組提供了一個頂層 mock 匯出,它是一個 MockTracker 例項。每個測試也透過其測試上下文的 mock 屬性提供自己的 MockTracker 例項。
mock.fn([original[, implementation]][, options])#
original<Function> | <AsyncFunction> 一個可選的函式,在其上建立模擬。預設值: 一個空操作函式。implementation<Function> | <AsyncFunction> 一個可選的函式,用作original的模擬實現。這對於建立在指定呼叫次數內表現出一種行為,然後恢復original行為的模擬很有用。預設值:original指定的函式。options<Object> 模擬函式的可選配置選項。支援以下屬性:times<integer> 模擬將使用implementation行為的次數。一旦模擬函式被呼叫了times次,它將自動恢復original的行為。此值必須是大於零的整數。預設值:Infinity。
- 返回:<Proxy> 被模擬的函式。被模擬的函式包含一個特殊的
mock屬性,它是MockFunctionContext的例項,可用於檢查和更改被模擬函式的行為。
此函式用於建立模擬函式。
以下示例建立一個模擬函式,每次呼叫時將計數器加一。times 選項用於修改模擬行為,使得前兩次呼叫將計數器加二而不是一。
test('mocks a counting function', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne, addTwo, { times: 2 });
assert.strictEqual(fn(), 2);
assert.strictEqual(fn(), 4);
assert.strictEqual(fn(), 5);
assert.strictEqual(fn(), 6);
});
mock.getter(object, methodName[, implementation][, options])#
此函式是 MockTracker.method 的語法糖,其中 options.getter 設定為 true。
mock.method(object, methodName[, implementation][, options])#
object<Object> 其方法被模擬的物件。methodName<string> | <symbol>object上要模擬的方法的識別符號。如果object[methodName]不是函式,則丟擲錯誤。implementation<Function> | <AsyncFunction> 一個可選的函式,用作object[methodName]的模擬實現。預設值:object[methodName]指定的原始方法。options<Object> 模擬方法的可選配置選項。支援以下屬性:- 返回:<Proxy> 被 mock 的方法。被 mock 的方法包含一個特殊的
mock屬性,它是MockFunctionContext的例項,可用於檢查和更改被 mock 方法的行為。
此函式用於在現有物件方法上建立 mock。以下示例演示瞭如何在現有物件方法上建立 mock。
test('spies on an object method', (t) => {
const number = {
value: 5,
subtract(a) {
return this.value - a;
},
};
t.mock.method(number, 'subtract');
assert.strictEqual(number.subtract.mock.callCount(), 0);
assert.strictEqual(number.subtract(3), 2);
assert.strictEqual(number.subtract.mock.callCount(), 1);
const call = number.subtract.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3]);
assert.strictEqual(call.result, 2);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, number);
});
mock.module(specifier[, options])#
specifier<string> | <URL> 標識要 mock 的模組的字串。options<Object> mock 模組的可選配置選項。支援以下屬性:cache<boolean> 如果為false,每次呼叫require()或import()都會生成一個新的 mock 模組。如果為true,後續呼叫將返回相同的模組 mock,並且該 mock 模組會插入到 CommonJS 快取中。預設值:false。defaultExport<any> 一個可選值,用作 mock 模組的預設匯出。如果未提供此值,ESM mock 不包含預設匯出。如果 mock 是 CommonJS 或內建模組,此設定將用作module.exports的值。如果未提供此值,CJS 和內建 mock 將使用一個空物件作為module.exports的值。namedExports<Object> 一個可選物件,其鍵和值用於建立 mock 模組的命名匯出。如果 mock 是 CommonJS 或內建模組,這些值將被複制到module.exports上。因此,如果建立的 mock 同時具有命名匯出和非物件的預設匯出,當作為 CJS 或內建模組使用時,該 mock 將丟擲異常。
- 返回:<MockModuleContext> 一個可用於操作 mock 的物件。
此函式用於 mock ECMAScript 模組、CommonJS 模組、JSON 模組和 Node.js 內建模組的匯出。在 mock 之前對原始模組的任何引用都不會受到影響。為了啟用模組 mock,Node.js 必須使用 --experimental-test-module-mocks 命令列標誌啟動。
以下示例演示瞭如何為模組建立 mock。
test('mocks a builtin module in both module systems', async (t) => {
// Create a mock of 'node:readline' with a named export named 'fn', which
// does not exist in the original 'node:readline' module.
const mock = t.mock.module('node:readline', {
namedExports: { fn() { return 42; } },
});
let esmImpl = await import('node:readline');
let cjsImpl = require('node:readline');
// cursorTo() is an export of the original 'node:readline' module.
assert.strictEqual(esmImpl.cursorTo, undefined);
assert.strictEqual(cjsImpl.cursorTo, undefined);
assert.strictEqual(esmImpl.fn(), 42);
assert.strictEqual(cjsImpl.fn(), 42);
mock.restore();
// The mock is restored, so the original builtin module is returned.
esmImpl = await import('node:readline');
cjsImpl = require('node:readline');
assert.strictEqual(typeof esmImpl.cursorTo, 'function');
assert.strictEqual(typeof cjsImpl.cursorTo, 'function');
assert.strictEqual(esmImpl.fn, undefined);
assert.strictEqual(cjsImpl.fn, undefined);
});
mock.property(object, propertyName[, value])#
object<Object> 其值被 mock 的物件。propertyName<string> | <symbol>object上要 mock 的屬性的識別符號。value<any> 一個可選值,用作object[propertyName]的 mock 值。預設值: 原始屬性值。- 返回:<Proxy> 指向被 mock 物件的代理。被 mock 的物件包含一個特殊的
mock屬性,它是MockPropertyContext的例項,可用於檢查和更改被 mock 屬性的行為。
為一個物件上的屬性值建立 mock。這允許你跟蹤和控制對特定屬性的訪問,包括它被讀取(getter)或寫入(setter)的次數,並在 mock 後恢復原始值。
test('mocks a property value', (t) => {
const obj = { foo: 42 };
const prop = t.mock.property(obj, 'foo', 100);
assert.strictEqual(obj.foo, 100);
assert.strictEqual(prop.mock.accessCount(), 1);
assert.strictEqual(prop.mock.accesses[0].type, 'get');
assert.strictEqual(prop.mock.accesses[0].value, 100);
obj.foo = 200;
assert.strictEqual(prop.mock.accessCount(), 2);
assert.strictEqual(prop.mock.accesses[1].type, 'set');
assert.strictEqual(prop.mock.accesses[1].value, 200);
prop.mock.restore();
assert.strictEqual(obj.foo, 42);
});
mock.reset()#
此函式恢復所有先前由此 MockTracker 建立的 mock 的預設行為,並將這些 mock 與 MockTracker 例項解除關聯。一旦解除關聯,這些 mock 仍然可以使用,但 MockTracker 例項不能再用於重置它們的行為或以其他方式與它們互動。
在每個測試完成後,此函式會在測試上下文的 MockTracker 上被呼叫。如果全域性 MockTracker 被廣泛使用,建議手動呼叫此函式。
mock.restoreAll()#
此函式恢復所有先前由此 MockTracker 建立的 mock 的預設行為。與 mock.reset() 不同,mock.restoreAll() 不會將 mock 與 MockTracker 例項解除關聯。
mock.setter(object, methodName[, implementation][, options])#
此函式是 MockTracker.method 的語法糖,其中 options.setter 設定為 true。
類:MockTimers#
模擬定時器是軟體測試中常用的一種技術,用於模擬和控制定時器(如 setInterval 和 setTimeout)的行為,而無需實際等待指定的時間間隔。
MockTimers 也能夠 mock Date 物件。
MockTracker 提供一個頂層 timers 匯出,它是一個 MockTimers 例項。
timers.enable([enableOptions])#
為指定的計時器啟用 mock 功能。
enableOptions<Object> 啟用計時器 mock 的可選配置選項。支援以下屬性:apis<Array> 一個包含要 mock 的計時器的可選陣列。當前支援的計時器值為'setInterval'、'setTimeout'、'setImmediate'和'Date'。預設值:['setInterval', 'setTimeout', 'setImmediate', 'Date']。如果未提供陣列,預設將 mock 所有與時間相關的 API('setInterval'、'clearInterval'、'setTimeout'、'clearTimeout'、'setImmediate'、'clearImmediate'和'Date')。now<number> | <Date> 一個可選的數字或 Date 物件,表示用作Date.now()值的初始時間(以毫秒為單位)。預設值:0。
注意: 當你為特定計時器啟用 mock 時,其關聯的清除函式也將被隱式 mock。
注意: mock Date 會影響被 mock 計時器的行為,因為它們使用相同的內部時鐘。
不設定初始時間的使用示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['setInterval'] });const { mock } = require('node:test');
mock.timers.enable({ apis: ['setInterval'] });
上述示例為 setInterval 計時器啟用 mock,並隱式 mock 了 clearInterval 函式。只有來自 node:timers、node:timers/promises 和 globalThis 的 setInterval 和 clearInterval 函式會被 mock。
設定初始時間的使用示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: 1000 });const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: 1000 });
使用初始 Date 物件作為時間設定的示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: new Date() });const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: new Date() });
或者,如果你不帶任何引數呼叫 mock.timers.enable()
所有計時器('setInterval'、'clearInterval'、'setTimeout'、'clearTimeout'、'setImmediate' 和 'clearImmediate')都將被 mock。來自 node:timers、node:timers/promises 和 globalThis 的 setInterval、clearInterval、setTimeout、clearTimeout、setImmediate 和 clearImmediate 函式將被 mock。以及全域性 Date 物件。
timers.reset()#
此函式恢復所有先前由此 MockTimers 例項建立的 mock 的預設行為,並將這些 mock 與 MockTracker 例項解除關聯。
注意: 在每個測試完成後,此函式會在測試上下文的 MockTracker 上被呼叫。
import { mock } from 'node:test';
mock.timers.reset();const { mock } = require('node:test');
mock.timers.reset();
timers[Symbol.dispose]()#
呼叫 timers.reset()。
timers.tick([milliseconds])#
為所有被 mock 的計時器推進時間。
milliseconds<number> 推進計時器的時間量,以毫秒為單位。預設值:1。
注意: 這與 Node.js 中 setTimeout 的行為不同,只接受正數。在 Node.js 中,支援帶有負數的 setTimeout 僅是為了 web 相容性。
以下示例 mock 了一個 setTimeout 函式,並使用 .tick 來推進時間,觸發所有待處理的計時器。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
另外,.tick 函式可以被多次呼叫
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
const nineSecs = 9000;
setTimeout(fn, nineSecs);
const threeSeconds = 3000;
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
assert.strictEqual(fn.mock.callCount(), 1);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
const nineSecs = 9000;
setTimeout(fn, nineSecs);
const threeSeconds = 3000;
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
assert.strictEqual(fn.mock.callCount(), 1);
});
使用 .tick 推進時間也會推進在 mock 啟用後建立的任何 Date 物件的時間(如果 Date 也被設定為 mock)。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 9999);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 9999);
});
使用清除函式#
如前所述,計時器中的所有清除函式(clearTimeout、clearInterval 和 clearImmediate)都會被隱式 mock。請看這個使用 setTimeout 的例子
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
const id = setTimeout(fn, 9999);
// Implicitly mocked as well
clearTimeout(id);
context.mock.timers.tick(9999);
// As that setTimeout was cleared the mock function will never be called
assert.strictEqual(fn.mock.callCount(), 0);
});const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
const id = setTimeout(fn, 9999);
// Implicitly mocked as well
clearTimeout(id);
context.mock.timers.tick(9999);
// As that setTimeout was cleared the mock function will never be called
assert.strictEqual(fn.mock.callCount(), 0);
});
使用 Node.js 計時器模組#
一旦你啟用了計時器 mock,node:timers、node:timers/promises 模組以及來自 Node.js 全域性上下文的計時器都將被啟用
注意: 此 API 目前不支援解構函式,例如 import { setTimeout } from 'node:timers'。
import assert from 'node:assert';
import { test } from 'node:test';
import nodeTimers from 'node:timers';
import nodeTimersPromises from 'node:timers/promises';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => {
const globalTimeoutObjectSpy = context.mock.fn();
const nodeTimerSpy = context.mock.fn();
const nodeTimerPromiseSpy = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(globalTimeoutObjectSpy, 9999);
nodeTimers.setTimeout(nodeTimerSpy, 9999);
const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1);
assert.strictEqual(nodeTimerSpy.mock.callCount(), 1);
await promise;
assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1);
});const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimers = require('node:timers');
const nodeTimersPromises = require('node:timers/promises');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => {
const globalTimeoutObjectSpy = context.mock.fn();
const nodeTimerSpy = context.mock.fn();
const nodeTimerPromiseSpy = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(globalTimeoutObjectSpy, 9999);
nodeTimers.setTimeout(nodeTimerSpy, 9999);
const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1);
assert.strictEqual(nodeTimerSpy.mock.callCount(), 1);
await promise;
assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1);
});
在 Node.js 中,來自 node:timers/promises 的 setInterval 是一個 AsyncGenerator,此 API 也支援它
import assert from 'node:assert';
import { test } from 'node:test';
import nodeTimersPromises from 'node:timers/promises';
test('should tick five times testing a real use case', async (context) => {
context.mock.timers.enable({ apis: ['setInterval'] });
const expectedIterations = 3;
const interval = 1000;
const startedAt = Date.now();
async function run() {
const times = [];
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
times.push(time);
if (times.length === expectedIterations) break;
}
return times;
}
const r = run();
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
const timeResults = await r;
assert.strictEqual(timeResults.length, expectedIterations);
for (let it = 1; it < expectedIterations; it++) {
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
}
});const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimersPromises = require('node:timers/promises');
test('should tick five times testing a real use case', async (context) => {
context.mock.timers.enable({ apis: ['setInterval'] });
const expectedIterations = 3;
const interval = 1000;
const startedAt = Date.now();
async function run() {
const times = [];
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
times.push(time);
if (times.length === expectedIterations) break;
}
return times;
}
const r = run();
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
const timeResults = await r;
assert.strictEqual(timeResults.length, expectedIterations);
for (let it = 1; it < expectedIterations; it++) {
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
}
});
timers.runAll()#
立即觸發所有待處理的 mock 計時器。如果 Date 物件也被 mock,它也會將 Date 物件推進到最遠計時器的時間。
下面的示例立即觸發所有待處理的計時器,使它們無需任何延遲地執行。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
// Notice that if both timers have the same timeout,
// the order of execution is guaranteed
setTimeout(() => results.push(3), 8888);
setTimeout(() => results.push(2), 8888);
assert.deepStrictEqual(results, []);
context.mock.timers.runAll();
assert.deepStrictEqual(results, [3, 2, 1]);
// The Date object is also advanced to the furthest timer's time
assert.strictEqual(Date.now(), 9999);
});const assert = require('node:assert');
const { test } = require('node:test');
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
// Notice that if both timers have the same timeout,
// the order of execution is guaranteed
setTimeout(() => results.push(3), 8888);
setTimeout(() => results.push(2), 8888);
assert.deepStrictEqual(results, []);
context.mock.timers.runAll();
assert.deepStrictEqual(results, [3, 2, 1]);
// The Date object is also advanced to the furthest timer's time
assert.strictEqual(Date.now(), 9999);
});
注意: runAll() 函式是專門為在計時器 mock 上下文中觸發計時器而設計的。它對即時系統時鐘或 mock 環境之外的實際計時器沒有任何影響。
timers.setTime(milliseconds)#
設定將用作任何被 mock 的 Date 物件的參考的當前 Unix 時間戳。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
const now = Date.now();
const setTime = 1000;
// Date.now is not mocked
assert.deepStrictEqual(Date.now(), now);
context.mock.timers.enable({ apis: ['Date'] });
context.mock.timers.setTime(setTime);
// Date.now is now 1000
assert.strictEqual(Date.now(), setTime);
});const assert = require('node:assert');
const { test } = require('node:test');
test('setTime replaces current time', (context) => {
const now = Date.now();
const setTime = 1000;
// Date.now is not mocked
assert.deepStrictEqual(Date.now(), now);
context.mock.timers.enable({ apis: ['Date'] });
context.mock.timers.setTime(setTime);
// Date.now is now 1000
assert.strictEqual(Date.now(), setTime);
});
日期和計時器協同工作#
日期和計時器物件是相互依賴的。如果你使用 setTime() 將當前時間傳遞給被 mock 的 Date 物件,用 setTimeout 和 setInterval 設定的計時器將不會受到影響。
然而,tick 方法將會推進被 mock 的 Date 物件。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
assert.deepStrictEqual(results, []);
context.mock.timers.setTime(12000);
assert.deepStrictEqual(results, []);
// The date is advanced but the timers don't tick
assert.strictEqual(Date.now(), 12000);
});const assert = require('node:assert');
const { test } = require('node:test');
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
assert.deepStrictEqual(results, []);
context.mock.timers.setTime(12000);
assert.deepStrictEqual(results, []);
// The date is advanced but the timers don't tick
assert.strictEqual(Date.now(), 12000);
});
類:TestsStream#
- 繼承自 <Readable>
成功呼叫 run() 方法將返回一個新的 <TestsStream> 物件,它會流式傳輸一系列表示測試執行的事件。TestsStream 將按照測試定義的順序發出事件。
一些事件保證按照測試定義的相同順序發出,而其他事件則按照測試執行的順序發出。
事件:'test:coverage'#
data<Object>summary<Object> 包含覆蓋率報告的物件。files<Array> 單個檔案覆蓋率報告的陣列。每個報告都是一個具有以下結構的物件:path<string> 檔案的絕對路徑。totalLineCount<number> 總行數。totalBranchCount<number> 總分支數。totalFunctionCount<number> 總函式數。coveredLineCount<number> 覆蓋的行數。coveredBranchCount<number> 覆蓋的分支數。coveredFunctionCount<number> 覆蓋的函式數。coveredLinePercent<number> 行覆蓋率百分比。coveredBranchPercent<number> 分支覆蓋率百分比。coveredFunctionPercent<number> 函式覆蓋率百分比。functions<Array> 表示函式覆蓋率的函式陣列。branches<Array> 表示分支覆蓋率的分支陣列。lines<Array> 表示行號及其被覆蓋次數的行陣列。
thresholds<Object> 包含每種覆蓋型別是否達標的物件。totals<Object> 包含所有檔案覆蓋率摘要的物件。totalLineCount<number> 總行數。totalBranchCount<number> 總分支數。totalFunctionCount<number> 總函式數。coveredLineCount<number> 覆蓋的行數。coveredBranchCount<number> 覆蓋的分支數。coveredFunctionCount<number> 覆蓋的函式數。coveredLinePercent<number> 行覆蓋率百分比。coveredBranchPercent<number> 分支覆蓋率百分比。coveredFunctionPercent<number> 函式覆蓋率百分比。
workingDirectory<string> 程式碼覆蓋率開始時的工作目錄。這對於在測試更改了 Node.js 程序工作目錄的情況下顯示相對路徑名很有用。
nesting<number> 測試的巢狀層級。
當代碼覆蓋率啟用且所有測試完成後發出。
事件:'test:complete'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。details<Object> 附加的執行元資料。passed<boolean> 測試是否透過。duration_ms<number> 測試的持續時間(以毫秒為單位)。error<Error> | <undefined> 如果測試未透過,則為一個包裝了測試丟擲的錯誤的錯誤物件。cause<Error> 測試實際丟擲的錯誤。
type<string> | <undefined> 測試的型別,用於表示這是否是一個測試套件。
file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。testNumber<number> 測試的序數。todo<string> | <boolean> | <undefined> 如果呼叫了context.todo,則存在此欄位。skip<string> | <boolean> | <undefined> 如果呼叫了context.skip,則存在此欄位。
當一個測試完成其執行時發出。此事件不是按照測試定義的順序發出的。相應的按宣告順序的事件是 'test:pass' 和 'test:fail'。
事件:'test:dequeue'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。type<string> 測試型別。可以是'suite'或'test'。
當一個測試出隊,即將執行之前發出。此事件不保證按照測試定義的順序發出。相應的按宣告順序的事件是 'test:start'。
事件:'test:diagnostic'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。message<string> 診斷資訊。nesting<number> 測試的巢狀層級。level<string> 診斷資訊的嚴重性級別。可能的值有:'info':資訊性訊息。'warn':警告。'error':錯誤。
當呼叫 context.diagnostic 時發出。此事件保證按照測試定義的順序發出。
事件:'test:enqueue'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。type<string> 測試型別。可以是'suite'或'test'。
當一個測試被加入執行佇列時發出。
事件:'test:fail'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。details<Object> 附加的執行元資料。duration_ms<number> 測試的持續時間(以毫秒為單位)。error<Error> 一個包裝了測試丟擲的錯誤的錯誤物件。cause<Error> 測試實際丟擲的錯誤。
type<string> | <undefined> 測試的型別,用於表示這是否是一個測試套件。attempt<number> | <undefined> 測試執行的嘗試次數,僅在使用--test-rerun-failures標誌時存在。
file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。testNumber<number> 測試的序數。todo<string> | <boolean> | <undefined> 如果呼叫了context.todo,則存在此欄位。skip<string> | <boolean> | <undefined> 如果呼叫了context.skip,則存在此欄位。
當一個測試失敗時發出。此事件保證按照測試定義的順序發出。相應的按執行順序的事件是 'test:complete'。
事件:'test:pass'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。details<Object> 附加的執行元資料。duration_ms<number> 測試的持續時間(以毫秒為單位)。type<string> | <undefined> 測試的型別,用於表示這是否是一個測試套件。attempt<number> | <undefined> 測試執行的嘗試次數,僅在使用--test-rerun-failures標誌時存在。passed_on_attempt<number> | <undefined> 測試透過的嘗試次數,僅在使用--test-rerun-failures標誌時存在。
file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。testNumber<number> 測試的序數。todo<string> | <boolean> | <undefined> 如果呼叫了context.todo,則存在此欄位。skip<string> | <boolean> | <undefined> 如果呼叫了context.skip,則存在此欄位。
當一個測試透過時發出。此事件保證按照測試定義的順序發出。相應的按執行順序的事件是 'test:complete'。
事件:'test:plan'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。nesting<number> 測試的巢狀層級。count<number> 已執行的子測試數量。
當給定測試的所有子測試都完成時發出。此事件保證按照測試定義的順序發出。
事件:'test:start'#
data<Object>column<number> | <undefined> 定義測試的列號,如果測試是透過 REPL 執行的,則為undefined。file<string> | <undefined> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為undefined。line<number> | <undefined> 定義測試的行號,如果測試是透過 REPL 執行的,則為undefined。name<string> 測試名稱。nesting<number> 測試的巢狀層級。
當一個測試開始報告其自身及其子測試的狀態時發出。此事件保證按照測試定義的順序發出。相應的按執行順序的事件是 'test:dequeue'。
事件:'test:stderr'#
當正在執行的測試寫入 stderr 時發出。此事件僅在傳遞了 --test 標誌時發出。此事件不保證按照測試定義的順序發出。
事件:'test:stdout'#
當正在執行的測試寫入 stdout 時發出。此事件僅在傳遞了 --test 標誌時發出。此事件不保證按照測試定義的順序發出。
事件:'test:summary'#
data<Object>counts<Object> 包含各種測試結果計數的物件。duration_ms<number> 測試執行的持續時間(以毫秒為單位)。file<string> | <undefined> 生成摘要的測試檔案的路徑。如果摘要對應多個檔案,則此值為undefined。success<boolean> 指示測試執行是否被視為成功。如果發生任何錯誤情況,例如測試失敗或未滿足覆蓋率閾值,此值將設定為false。
當測試執行完成時發出。此事件包含與已完成測試執行相關的指標,對於確定測試執行是否透過或失敗很有用。如果使用程序級測試隔離,除了最終的累積摘要外,還會為每個測試檔案生成一個 'test:summary' 事件。
事件:'test:watch:drained'#
在監視模式下,當沒有更多測試排隊等待執行時發出。
事件:'test:watch:restarted'#
在監視模式下,由於檔案更改而重新啟動一個或多個測試時發出。
類:TestContext#
TestContext 的例項會傳遞給每個測試函式,以便與測試執行器互動。但是,TestContext 建構函式並未作為 API 的一部分公開。
context.before([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。此函式的第一個引數是TestContext物件。如果鉤子使用回撥,回撥函式將作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式用於建立一個在當前測試的子測試之前執行的鉤子。
context.beforeEach([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。此函式的第一個引數是TestContext物件。如果鉤子使用回撥,回撥函式將作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式用於建立一個在當前測試的每個子測試之前執行的鉤子。
test('top level test', async (t) => {
t.beforeEach((t) => t.diagnostic(`about to run ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
},
);
});
context.after([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。此函式的第一個引數是TestContext物件。如果鉤子使用回撥,回撥函式將作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式用於建立一個在當前測試完成後執行的鉤子。
test('top level test', async (t) => {
t.after((t) => t.diagnostic(`finished running ${t.name}`));
assert.ok('some relevant assertion here');
});
context.afterEach([fn][, options])#
fn<Function> | <AsyncFunction> 鉤子函式。此函式的第一個引數是TestContext物件。如果鉤子使用回撥,回撥函式將作為第二個引數傳遞。預設值: 一個空操作函式。options<Object> 鉤子的配置選項。支援以下屬性:signal<AbortSignal> 允許中止正在進行的鉤子。timeout<number> 鉤子將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。
此函式用於建立一個在當前測試的每個子測試之後執行的鉤子。
test('top level test', async (t) => {
t.afterEach((t) => t.diagnostic(`finished running ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
},
);
});
context.assert#
一個包含繫結到 context 的斷言方法的物件。node:assert 模組的頂層函式在這裡公開,用於建立測試計劃。
test('test', (t) => {
t.plan(1);
t.assert.strictEqual(true, true);
});
context.assert.fileSnapshot(value, path[, options])#
value<any> 要序列化為字串的值。如果 Node.js 是用--test-update-snapshots標誌啟動的,序列化的值將被寫入path。否則,序列化的值將與現有快照檔案的內容進行比較。path<string> 序列化的value被寫入的檔案。options<Object> 可選的配置選項。支援以下屬性:serializers<Array> 用於將value序列化為字串的同步函式陣列。value作為唯一引數傳遞給第一個序列化函式。每個序列化器的返回值將作為輸入傳遞給下一個序列化器。所有序列化器執行完畢後,最終結果將被強制轉換為字串。預設值: 如果未提供序列化器,則使用測試執行器的預設序列化器。
此函式序列化 value 並將其寫入由 path 指定的檔案。
test('snapshot test with default serialization', (t) => {
t.assert.fileSnapshot({ value1: 1, value2: 2 }, './snapshots/snapshot.json');
});
此函式與 context.assert.snapshot() 在以下方面有所不同:
- 快照檔案路徑由使用者明確提供。
- 每個快照檔案僅限於一個快照值。
- 測試執行器不會執行額外的轉義。
這些差異使得快照檔案能夠更好地支援語法高亮等功能。
context.assert.snapshot(value[, options])#
value<any> 要序列化為字串的值。如果 Node.js 是用--test-update-snapshots標誌啟動的,序列化的值將被寫入快照檔案。否則,序列化的值將與現有快照檔案中相應的值進行比較。options<Object> 可選的配置選項。支援以下屬性:serializers<Array> 用於將value序列化為字串的同步函式陣列。value作為唯一引數傳遞給第一個序列化函式。每個序列化器的返回值將作為輸入傳遞給下一個序列化器。所有序列化器執行完畢後,最終結果將被強制轉換為字串。預設值: 如果未提供序列化器,則使用測試執行器的預設序列化器。
此函式實現了快照測試的斷言。
test('snapshot test with default serialization', (t) => {
t.assert.snapshot({ value1: 1, value2: 2 });
});
test('snapshot test with custom serialization', (t) => {
t.assert.snapshot({ value3: 3, value4: 4 }, {
serializers: [(value) => JSON.stringify(value)],
});
});
context.diagnostic(message)#
message<string> 要報告的訊息。
此函式用於將診斷資訊寫入輸出。任何診斷資訊都包含在測試結果的末尾。此函式不返回值。
test('top level test', (t) => {
t.diagnostic('A diagnostic message');
});
context.filePath#
建立當前測試的測試檔案的絕對路徑。如果一個測試檔案匯入了其他生成測試的模組,匯入的測試將返回根測試檔案的路徑。
context.fullName#
測試的名稱及其每個祖先的名稱,用 > 分隔。
context.name#
測試的名稱。
context.plan(count[,options])#
此函式用於設定測試中預期執行的斷言和子測試的數量。如果執行的斷言和子測試數量與預期數量不匹配,測試將失敗。
注意:為確保斷言被跟蹤,必須使用
t.assert而不是直接使用assert。
test('top level test', (t) => {
t.plan(2);
t.assert.ok('some relevant assertion here');
t.test('subtest', () => {});
});
在處理非同步程式碼時,plan 函式可用於確保執行正確數量的斷言:
test('planning with streams', (t, done) => {
function* generate() {
yield 'a';
yield 'b';
yield 'c';
}
const expected = ['a', 'b', 'c'];
t.plan(expected.length);
const stream = Readable.from(generate());
stream.on('data', (chunk) => {
t.assert.strictEqual(chunk, expected.shift());
});
stream.on('end', () => {
done();
});
});
使用 wait 選項時,你可以控制測試等待預期斷言的時間。例如,設定最大等待時間可以確保測試在指定的時間範圍內等待非同步斷言完成:
test('plan with wait: 2000 waits for async assertions', (t) => {
t.plan(1, { wait: 2000 }); // Waits for up to 2 seconds for the assertion to complete.
const asyncActivity = () => {
setTimeout(() => {
t.assert.ok(true, 'Async assertion completed within the wait time');
}, 1000); // Completes after 1 second, within the 2-second wait time.
};
asyncActivity(); // The test will pass because the assertion is completed in time.
});
注意:如果指定了 wait 超時,它僅在測試函式執行完畢後才開始倒計時。
context.runOnly(shouldRunOnlyTests)#
shouldRunOnlyTests<boolean> 是否只執行帶有only選項的測試。
如果 shouldRunOnlyTests 為真值,測試上下文將只執行設定了 only 選項的測試。否則,所有測試都將執行。如果 Node.js 不是用 --test-only 命令列選項啟動的,則此函式為空操作。
test('top level test', (t) => {
// The test context can be set to run subtests with the 'only' option.
t.runOnly(true);
return Promise.all([
t.test('this subtest is now skipped'),
t.test('this subtest is run', { only: true }),
]);
});
context.signal#
可用於在測試被中止時中止測試子任務。
test('top level test', async (t) => {
await fetch('some/uri', { signal: t.signal });
});
context.skip([message])#
message<string> 可選的跳過訊息。
此函式使測試的輸出指示該測試被跳過。如果提供了 message,它將包含在輸出中。呼叫 skip() 不會終止測試函式的執行。此函式不返回值。
test('top level test', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip('this is skipped');
});
context.todo([message])#
message<string> 可選的TODO訊息。
此函式向測試的輸出新增一個 TODO 指令。如果提供了 message,它將包含在輸出中。呼叫 todo() 不會終止測試函式的執行。此函式不返回值。
test('top level test', (t) => {
// This test is marked as `TODO`
t.todo('this is a todo');
});
context.test([name][, options][, fn])#
name<string> 子測試的名稱,在報告測試結果時顯示。預設值:fn的name屬性,如果fn沒有名稱,則為'<anonymous>'。options<Object> 子測試的配置選項。支援以下屬性:concurrency<number> | <boolean> | <null> 如果提供一個數字,則在應用程式執行緒內將並行執行那麼多數量的測試。如果為true,將並行執行所有子測試。如果為false,一次只執行一個測試。如果未指定,子測試將從其父測試繼承此值。預設值:null。only<boolean> 如果為真值,並且測試上下文配置為只執行only測試,則此測試將被執行。否則,該測試將被跳過。預設值:false。signal<AbortSignal> 允許中止正在進行的測試。skip<boolean> | <string> 如果為真值,則跳過該測試。如果提供一個字串,該字串將作為跳過測試的原因顯示在測試結果中。預設值:false。todo<boolean> | <string> 如果為真值,則將測試標記為TODO。如果提供一個字串,該字串將作為測試為TODO的原因顯示在測試結果中。預設值:false。timeout<number> 測試將在多少毫秒後失敗。如果未指定,子測試將從其父級繼承此值。預設值:Infinity。plan<number> 預計在測試中執行的斷言和子測試的數量。如果測試中執行的斷言數量與計劃中指定的數量不匹配,測試將失敗。預設值:undefined。
fn<Function> | <AsyncFunction> 被測試的函式。此函式的第一個引數是TestContext物件。如果測試使用回撥,則回撥函式作為第二個引數傳遞。預設值: 一個空操作函式。- 返回:<Promise> 在測試完成後以
undefined兌現。
此函式用於在當前測試下建立子測試。此函式的行為與頂層 test() 函式相同。
test('top level test', async (t) => {
await t.test(
'This is a subtest',
{ only: false, skip: false, concurrency: 1, todo: false, plan: 1 },
(t) => {
t.assert.ok('some relevant assertion here');
},
);
});
context.waitFor(condition[, options])#
condition<Function> | <AsyncFunction> 一個斷言函式,它會週期性地被呼叫,直到它成功完成或定義的輪詢超時。成功完成定義為不丟擲錯誤或拒絕。此函式不接受任何引數,並允許返回任何值。options<Object> 輪詢操作的可選配置物件。支援以下屬性:- 返回:<Promise> 以
condition返回的值兌現。
此方法輪詢一個 condition 函式,直到該函式成功返回或操作超時。
類:SuiteContext#
SuiteContext 的例項會傳遞給每個測試套件函式,以便與測試執行器互動。但是,SuiteContext 建構函式並未作為 API 的一部分公開。
context.filePath#
建立當前測試套件的測試檔案的絕對路徑。如果一個測試檔案匯入了其他生成測試套件的模組,匯入的套件將返回根測試檔案的路徑。
context.name#
測試套件的名稱。