Node.js v21.7.2 文件
- Node.js v21.7.2
-
► 目錄
- 測試執行器
- 子測試
- 略過測試
describe
/it
語法only
測試- 依名稱篩選測試
- 多餘的非同步活動
- 監控模式
- 從命令列執行測試
- 收集程式碼覆蓋率
- 模擬
- 測試報告器
run([選項])
test([名稱][, 選項][, fn])
test.skip([名稱][, 選項][, fn])
test.todo([名稱][, 選項][, fn])
test.only([名稱][, 選項][, fn])
describe([名稱][, 選項][, fn])
describe.skip([名稱][, 選項][, fn])
describe.todo([名稱][, 選項][, fn])
describe.only([名稱][, 選項][, fn])
it([名稱][, 選項][, fn])
it.skip([名稱][, 選項][, fn])
it.todo([名稱][, 選項][, fn])
it.only([名稱][, 選項][, fn])
before([fn][, 選項])
after([fn][, 選項])
beforeEach([fn][, 選項])
afterEach([fn][, 選項])
- 類別:
MockFunctionContext
- 類別:
MockTracker
- 類別:
MockTimers
- 類別:
TestsStream
- 類別:
TestContext
context.before([fn][, options])
context.beforeEach([fn][, options])
context.after([fn][, options])
context.afterEach([fn][, options])
context.diagnostic(message)
context.name
context.runOnly(shouldRunOnlyTests)
context.signal
context.skip([message])
context.todo([message])
context.test([name][, options][, fn])
- 類別:
SuiteContext
- 測試執行器
-
► 索引
- 斷言測試
- 非同步內容追蹤
- 非同步掛鉤
- Buffer
- C++ 外掛程式
- 使用 Node-API 的 C/C++ 外掛程式
- C++ 嵌入式 API
- 子程序
- 叢集
- 命令列選項
- 主控台
- Corepack
- Crypto
- 偵錯器
- 已棄用的 API
- 診斷頻道
- DNS
- Domain
- 錯誤
- 事件
- 檔案系統
- 全域變數
- HTTP
- HTTP/2
- HTTPS
- 檢查器
- 國際化
- 模組:CommonJS 模組
- 模組:ECMAScript 模組
- 模組:
node:module
API - 模組:套件
- Net
- OS
- Path
- 效能掛鉤
- 權限
- 程序
- Punycode
- 查詢字串
- Readline
- REPL
- 報告
- 單一可執行應用程式
- 串流
- 字串解碼器
- 測試執行器
- 計時器
- TLS/SSL
- 追蹤事件
- TTY
- UDP/資料報
- URL
- 公用程式
- V8
- VM
- 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:
架構下可用。以下程式碼無法執行
import test from 'test';
const test = require('test');
透過 test
模組建立的測試包含一個單一函式,該函式會以三種方式之一進行處理
- 同步函式,如果擲回例外狀況,則視為失敗,否則視為通過。
- 傳回
Promise
的函式,如果Promise
拒絕,則視為失敗,如果Promise
完成,則視為通過。 - 接收回呼函式的函式。如果回呼函式將任何真值作為其第一個引數接收,則測試視為失敗。如果假值作為第一個引數傳遞給回呼函式,則測試視為通過。如果測試函式接收回呼函式,且也傳回
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
勾子會在每個子測試執行之間觸發。
在此範例中,await
用於確保兩個子測試都已完成。這是必要的,因為父測試不會等待其子測試完成,這與使用 describe
和 it
語法建立的測試不同。當父測試完成時,任何仍未完成的子測試都會被取消並視為失敗。任何子測試失敗都會導致父測試失敗。
略過測試#
個別測試可以透過傳遞 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');
});
describe
/it
語法#
執行測試也可以使用 describe
來宣告套件,並使用 it
來宣告測試。套件用於組織和將相關測試分組在一起。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');
僅測試#
如果 Node.js 以 --test-only
命令列選項啟動,則可以略過所有頂層測試,只執行傳遞 only
選項給應執行的測試的選定子集。當執行設定 only
選項的測試時,也會執行所有子測試。測試內容的 runOnly()
方法可用於在子測試層級實作相同的行為。
// Assume Node.js is run with the --test-only command-line option.
// The 'only' option is set, so this test is 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');
});
依名稱篩選測試#
可以使用 --test-name-pattern
命令列選項,只執行名稱符合所提供模式的測試。測試名稱模式會解譯為 JavaScript 正規表示式。可以多次指定 --test-name-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"
啟動 Node.js 會符合 Test 4
和 Test 5
,因為模式不分大小寫。
測試名稱模式不會變更測試執行器執行的檔案集。
外部非同步活動#
測試函數執行完畢後,結果會盡快回報,同時維持測試順序。然而,測試函數可能會產生非同步活動,而這些活動會比測試本身更長命。測試執行器會處理這類活動,但不會延遲回報測試結果以配合這些活動。
在以下範例中,一個測試完成,但仍有兩個 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
在監控模式中,測試執行器會監控測試檔案及其相依項目的變更。當偵測到變更時,測試執行器會重新執行受到變更影響的測試。測試執行器會持續執行,直到程序終止。
從命令列執行測試#
Node.js 測試執行器可透過傳遞 --test
旗標從命令列呼叫
node --test
預設情況下,Node.js 會執行符合下列模式的所有檔案
**/*.test.?(c|m)js
**/*-test.?(c|m)js
**/*_test.?(c|m)js
**/test-*.?(c|m)js
**/test.?(c|m)js
**/test/**/*.?(c|m)js
或者,也可以將一個或多個 glob 模式提供為 Node.js 命令的最後一個引數,如下所示。Glob 模式遵循 glob(7)
的行為。
node --test **/*.test.js **/*.spec.js
符合的檔案會作為測試檔案執行。可以在 測試執行器執行模式 區段找到有關測試檔案執行的更多資訊。
測試執行器執行模式#
每個符合的測試檔案會在個別的子處理序中執行。在任何時間執行的子處理序最大數量由 --test-concurrency
旗標控制。如果子處理序以 0 的結束代碼結束,則測試會被視為通過。否則,測試會被視為失敗。測試檔案必須可由 Node.js 執行,但不需要在內部使用 node:test
模組。
每個測試檔案會像常規腳本一樣執行。也就是說,如果測試檔案本身使用 node:test
來定義測試,則所有這些測試都將在單一應用程式執行緒中執行,與 test()
的 concurrency
選項值無關。
收集程式碼覆蓋率#
當 Node.js 以 --experimental-test-coverage
命令列旗標啟動時,程式碼覆蓋率會被收集,並且在所有測試完成後會回報統計資料。如果 NODE_V8_COVERAGE
環境變數用於指定程式碼覆蓋率目錄,則產生的 V8 覆蓋率檔案會寫入該目錄。Node.js 核心模組和 node_modules/
目錄中的檔案不會包含在覆蓋率報告中。如果啟用覆蓋率,則覆蓋率報告會透過 '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
限制#
測試執行器的程式碼涵蓋範圍功能有下列限制,這些限制會在未來的 Node.js 版本中解決
- 不支援原始碼對應。
- 不支援從涵蓋範圍報告中排除特定檔案或目錄。
模擬#
node:test
模組支援在測試期間透過頂層 mock
物件進行模擬。下列範例建立一個間諜函式,將兩個數字加總。然後使用間諜函式來斷言函式呼叫符合預期。
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.calls.length, 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.calls.length, 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.calls.length, 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.calls.length, 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.calls.length, 0);
assert.strictEqual(number.add(3), 8);
assert.strictEqual(number.add.mock.calls.length, 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 模組中的 setTimeout
函式,以及來自 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'll 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。你可以透過將 now
屬性傳遞給 .enable()
方法來設定初始日期。這個值將會用作模擬 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);
});
測試報告器#
node:test
模組支援傳遞 --test-reporter
旗標,讓測試執行器使用特定的報告器。
支援下列內建報告器
-
tap
tap
報告器以 TAP 格式輸出測試結果。 -
spec
spec
報告器以人類可讀的格式輸出測試結果。 -
dot
dot
報告器以簡潔的格式輸出測試結果,其中每個通過的測試都以.
表示,每個失敗的測試都以X
表示。 -
junit
junit 報告器以 jUnit XML 格式輸出測試結果 -
lcov
lcov
報告器在搭配--experimental-test-coverage
旗標使用時,會輸出測試涵蓋率。
當 stdout
是 TTY 時,預設會使用 spec
報告器。否則,預設會使用 tap
報告器。
這些報告器的確切輸出可能會在不同版本的 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: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: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`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued`;
break;
case 'test:watch:drained':
yield 'test watch queue drained';
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';
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`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued`;
break;
case 'test:watch:drained':
yield 'test watch queue drained';
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
。files
: <Array> 包含要執行檔案清單的陣列。預設值與 測試執行器執行模式 相符的檔案。inspectPort
<number> | <Function> 設定測試子程序的檢查埠。這可以是數字,或是不帶參數且傳回數字的函式。如果提供 nullish 值,則每個程序會取得自己的埠,從主要程序的process.debugPort
開始遞增。預設值:undefined
。only
: <布林值> 如果為真,測試內容只會執行設定only
選項的測試setup
<函式> 接受TestsStream
執行個體的函式,可以在執行任何測試前用來設定監聽器。預設:未定義
。signal
<中斷訊號> 允許中斷進行中的測試執行。testNamePatterns
<字串> | <正規表示式> | <陣列> 字串、正規表示式或正規表示式陣列,可用於只執行名稱符合所提供模式的測試。測試名稱模式會被解釋為 JavaScript 正規表示式。對於執行的每個測試,任何對應的測試掛勾(例如beforeEach()
)也會執行。預設:未定義
。timeout
<數字> 測試執行將在經過此毫秒數後失敗。如果未指定,子測試會從其父項繼承此值。預設:無限
。watch
<布林值> 是否以監控模式執行。預設:false
。shard
<物件> 在特定分片中執行測試。預設:未定義
。
- 傳回:<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);
test([name][, options][, fn])
#
name
<字串> 測試名稱,會在報告測試結果時顯示。預設:fn
的name
屬性,如果fn
沒有名稱,則為'<匿名>'
。options
<物件> 測試的組態選項。支援下列屬性concurrency
<數字> | <布林> 如果提供數字,則會在應用程式執行緒中並行執行那麼多個測試。如果為true
,所有排定的非同步測試會在執行緒中並行執行。如果為false
,一次只會執行一個測試。如果未指定,子測試會從其父項繼承此值。預設:false
。only
<布林> 如果為真,且測試內容設定為只執行only
測試,則會執行此測試。否則,會略過此測試。預設:false
。signal
<中止訊號> 允許中止正在進行的測試。skip
<布林值> | <字串> 如果為真值,則會略過測試。如果提供字串,則會在測試結果中顯示該字串作為略過測試的原因。預設:false
。todo
<布林值> | <字串> 如果為真值,則會將測試標記為TODO
。如果提供字串,則會在測試結果中顯示該字串作為測試為TODO
的原因。預設:false
。timeout
<數字> 測試會在經過此毫秒數後失敗。如果未指定,子測試會從其父測試繼承此值。預設:Infinity
。
fn
<函式> | <非同步函式> 正在測試的函式。此函式的第一個引數是TestContext
物件。如果測試使用回呼,則會將回呼函式作為第二個引數傳遞。預設:無動作函式。- 傳回:<承諾> 在測試完成後以
undefined
履行,或是在describe()
中執行測試時立即履行。
test()
函式是從 test
模組匯入的值。每次呼叫此函式都會將測試報告給 <TestsStream>。
傳遞給 fn
引數的 TestContext
物件可用於執行與目前測試相關的動作。範例包括略過測試、新增額外的診斷資訊,或建立子測試。
test()
會傳回一個承諾,在測試完成後履行。如果在 describe()
區塊中呼叫 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])
#
name
<string> 套件的名稱,在報告測試結果時顯示。預設值:fn
的name
屬性,如果fn
沒有名稱,則為'<anonymous>'
。options
<Object> 套件的設定選項。支援與test([name][, options][, fn])
相同的選項。fn
<Function> | <AsyncFunction> 套件下的函式,宣告所有子測試和子套件。此函式的第一個引數是SuiteContext
物件。預設值:無操作函式。- 傳回:<Promise> 立即完成,值為
undefined
。
從 node:test
模組匯入的 describe()
函式。每次呼叫此函式都會建立一個子測試。在呼叫頂層 describe
函式後,所有頂層測試和套件都會執行。
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');
});
});
類別:MockFunctionContext
#
MockFunctionContext
類別用於檢查或操作透過 MockTracker
API 建立的模擬行為。
ctx.calls
#
取得用於追蹤對模擬進行呼叫的內部陣列副本的 getter。陣列中的每個項目都是具有下列屬性的物件。
arguments
<陣列> 傳遞給模擬函式的引數陣列。error
<any> 如果模擬函式引發例外狀況,此屬性會包含引發的值。預設值:undefined
。result
<any> 模擬函式傳回的值。stack
<Error>Error
物件,其堆疊可 used 用於判斷模擬函式呼叫的呼叫位置。target
<函式> | <undefined> 如果模擬函式是建構函式,此欄位會包含正在建構的類別。否則,此欄位會是undefined
。this
<any> 模擬函式的this
值。
ctx.callCount()
#
- 傳回:<整數> 呼叫此模擬的次數。
此函式傳回呼叫此模擬的次數。此函式比檢查 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()
#
重設模擬函數的實作至其原始行為。呼叫這個函數後,模擬仍可使用。
類別: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
的行為。此值必須大於 0 的整數。預設: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
屬性,它是MockFunctionContext
的一個實例,可用於檢查和變更模擬方法的行為。
此函式用於在現有物件方法上建立一個模擬。以下範例說明如何於現有物件方法上建立模擬。
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.calls.length, 0);
assert.strictEqual(number.subtract(3), 2);
assert.strictEqual(number.subtract.mock.calls.length, 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.reset()
#
此函式還原先前由這個 MockTracker
建立的所有模擬的預設行為,並解除模擬與 MockTracker
實例的關聯。解除關聯後,模擬仍然可以使用,但 MockTracker
實例無法再用於重設其行為或與其互動。
在每個測試完成後,此函式會在測試內容的 MockTracker
上呼叫。如果大量使用全域 MockTracker
,建議手動呼叫此函式。
mock.restoreAll()
#
此函式還原先前由這個 MockTracker
建立的所有模擬的預設行為。與 mock.reset()
不同,mock.restoreAll()
沒有解除模擬與 MockTracker
實例的關聯。
mock.setter(object, methodName[, implementation][, options])
#
此函數是 MockTracker.method
的語法糖,其中 options.setter
設為 true
。
類別:MockTimers
#
模擬計時器是軟體測試中常用的技術,用於模擬和控制計時器的行為,例如 setInterval
和 setTimeout
,而無需實際等待指定的時間間隔。
MockTimers 也能模擬 Date
物件。
MockTracker
提供頂層 timers
匯出,它是 MockTimers
執行個體。
timers.enable([enableOptions])
#
針對指定的計時器啟用計時器模擬。
enableOptions
<Object> 啟用計時器模擬的選用組態選項。支援下列屬性apis
<Array> 包含要模擬的計時器的選用陣列。目前支援的計時器值為'setInterval'
、'setTimeout'
、'setImmediate'
和'Date'
。預設:['setInterval', 'setTimeout', 'setImmediate', 'Date']
。如果未提供陣列,預設會模擬所有時間相關 API ('setInterval'
、'clearInterval'
、'setTimeout'
、'clearTimeout'
和'Date'
)。now
<數字> | <日期> 表示要作為Date.now()
值使用的初始時間(以毫秒為單位)的數字或日期物件(選用)。預設:0
。
注意:當您啟用特定計時器的模擬時,其關聯的清除函式也會被隱含地模擬。
注意:模擬 Date
會影響模擬計時器的行為,因為它們使用相同的內部時鐘。
不設定初始時間的範例用法
import { mock } from 'node:test';
mock.timers.enable({ apis: ['setInterval'] });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['setInterval'] });
上述範例啟用 setInterval
計時器的模擬,並隱含地模擬 clearInterval
函式。只有來自 node:timers、node:timers/promises 和 globalThis
的 setInterval
和 clearInterval
函式會被模擬。
設定初始時間的範例用法
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: 1000 });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: 1000 });
設定初始日期物件作為時間的範例用法
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'
)都會被模擬。來自 node:timers
、node:timers/promises
和 globalThis
的 setInterval
、clearInterval
、setTimeout
和 clearTimeout
函式會被模擬。以及全域 Date
物件。
timers.reset()
#
此函式會還原先前由這個 MockTimers
實例建立的所有模擬的預設行為,並解除模擬與 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)
#
為所有模擬計時器推進時間。
milliseconds
<number> 以毫秒為單位的時間量,用於推進計時器。
注意:這與 Node.js 中 setTimeout
的行為不同,且僅接受正數。在 Node.js 中,僅出於網頁相容性原因才支援帶有負數的 setTimeout
。
以下範例模擬 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 twoSeconds = 3000;
context.mock.timers.tick(twoSeconds);
context.mock.timers.tick(twoSeconds);
context.mock.timers.tick(twoSeconds);
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 twoSeconds = 3000;
context.mock.timers.tick(twoSeconds);
context.mock.timers.tick(twoSeconds);
context.mock.timers.tick(twoSeconds);
assert.strictEqual(fn.mock.callCount(), 1);
});
使用 .tick
推進時間也會推進在啟用模擬之後建立的任何 Date
物件的時間(如果 Date
也設定為模擬)。
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
)都隱含模擬。請查看使用 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);
// Implicity 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);
// Implicity 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 計時器模組#
啟用模擬計時器後,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()
#
立即觸發所有待處理的模擬計時器。如果 Date
物件也模擬,它也會將 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()
函式專門設計用於在計時器模擬的環境中觸發計時器。它不會對模擬環境之外的實際系統時鐘或實際計時器產生任何影響。
timers.setTime(milliseconds)
#
設定目前的 Unix 時間戳記,將用作任何模擬的 Date
物件的參考。
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()
將目前時間傳遞給模擬的 Date
物件,則使用 setTimeout
和 setInterval
設定的計時器不會受到影響。
不過,tick
方法會推進模擬的 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
<陣列> 各檔案的涵蓋率報告陣列。每個報告都是一個具有下列結構的物件path
<字串> 檔案的絕對路徑。totalLineCount
<數字> 總行數。totalBranchCount
<數字> 總分支數。totalFunctionCount
<數字> 總函式數。coveredLineCount
<數字> 涵蓋的行數。coveredBranchCount
<數字> 涵蓋的分支數。coveredFunctionCount
<數字> 涵蓋的函式數。coveredLinePercent
<數字> 涵蓋率百分比。coveredBranchPercent
<數字> 分支涵蓋率百分比。coveredFunctionPercent
<數字> 函式涵蓋率百分比。functions
<陣列> 表示函式涵蓋率的函式陣列。branches
<陣列> 表示分支涵蓋率的分支陣列。lines
<陣列> 代表行號和執行次數的陣列。
totals
<物件> 包含所有檔案涵蓋率摘要的物件。workingDirectory
<字串> 開始程式碼涵蓋率時的作業目錄。這有助於顯示相對路徑名稱,以防測試變更 Node.js 程序的作業目錄。
nesting
<數字> 測試的巢狀層級。
在啟用程式碼涵蓋率且所有測試都已完成時發出。
事件:'test:dequeue'
#
data
<Object>
在測試出列,準備執行之前發出。
事件:'test:diagnostic'
#
data
<Object>
在呼叫 context.diagnostic
時發出。
事件:'test:enqueue'
#
data
<Object>
在測試排隊準備執行時發出。
事件:'test:fail'
#
data
<Object>column
<數字> | <未定義> 定義測試的欄位號碼,或如果測試是透過 REPL 執行的,則為未定義
。details
<物件> 其他執行中繼資料。file
<字串> | <未定義> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為未定義
。line
<數字> | <未定義> 定義測試的行號,或如果測試是透過 REPL 執行,則為undefined
。name
<字串> 測試名稱。nesting
<數字> 測試的巢狀層級。testNumber
<數字> 測試的序號。todo
<字串> | <布林值> | <未定義> 如果呼叫context.todo
,則會顯示skip
<字串> | <布林值> | <未定義> 如果呼叫context.skip
,則會顯示
在測試失敗時發出。
事件:'test:pass'
#
data
<Object>column
<數字> | <未定義> 定義測試的欄位號碼,或如果測試是透過 REPL 執行的,則為未定義
。details
<物件> 其他執行中繼資料。file
<字串> | <未定義> 測試檔案的路徑,如果測試是透過 REPL 執行的,則為未定義
。line
<數字> | <未定義> 定義測試的行號,或如果測試是透過 REPL 執行,則為undefined
。name
<字串> 測試名稱。nesting
<數字> 測試的巢狀層級。testNumber
<數字> 測試的序號。todo
<字串> | <布林值> | <未定義> 如果呼叫context.todo
,則會顯示skip
<字串> | <布林值> | <未定義> 如果呼叫context.skip
,則會顯示
在測試通過時發出。
事件:'test:plan'
#
data
<Object>
在給定測試的所有子測試完成時發出。
事件:'test:start'
#
data
<Object>
在測試開始回報其自身及其子測試狀態時發出。保證此事件會按照測試定義的順序發出。
事件:'test:stderr'
#
data
<Object>
當執行的測試寫入 stderr
時發出。只有在傳遞 --test
旗標時才會發出此事件。
事件:'test:stdout'
#
data
<Object>
當執行的測試寫入 stdout
時發出。只有在傳遞 --test
旗標時才會發出此事件。
事件:'test:watch:drained'
#
在監控模式中沒有更多測試排隊執行時發出。
類別: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.diagnostic(message)
#
message
<string> 要報告的訊息。
此函式用於將診斷寫入輸出。任何診斷資訊都會包含在測試結果的結尾。此函式不會傳回值。
test('top level test', (t) => {
t.diagnostic('A diagnostic message');
});
context.name
#
測試名稱。
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
<布林> 如果為真,且測試內容設定為只執行only
測試,則會執行此測試。否則,會略過此測試。預設:false
。signal
<中止訊號> 允許中止正在進行的測試。skip
<布林值> | <字串> 如果為真值,則會略過測試。如果提供字串,則會在測試結果中顯示該字串作為略過測試的原因。預設:false
。todo
<布林值> | <字串> 如果為真值,則會將測試標記為TODO
。如果提供字串,則會在測試結果中顯示該字串作為測試為TODO
的原因。預設:false
。timeout
<數字> 測試會在經過此毫秒數後失敗。如果未指定,子測試會從其父測試繼承此值。預設:Infinity
。
fn
<函式> | <非同步函式> 正在測試的函式。此函式的第一個引數是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 },
(t) => {
assert.ok('some relevant assertion here');
},
);
});
類別:SuiteContext
#
SuiteContext
的執行個體會傳遞給每個套件函式,以便與測試執行器互動。但是,SuiteContext
建構函式並未公開為 API 的一部分。
context.name
#
套件的名稱。
context.signal
#
當測試已中止時,可用於中止測試子任務。