Node.js v21.7.2 文档
- Node.js v21.7.2
-
► 目录
- 测试运行器
- 子测试
- 跳过测试
describe
/it
语法only
测试- 按名称过滤测试
- 无关的异步活动
- 监视模式
- 从命令行运行测试
- 收集代码覆盖率
- 模拟
- 测试报告器
run([options])
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])
- 类:
MockFunctionContext
- 类:
MockTracker
- 类:
MockTimers
- 类:
TestsStream
- 类:
TestContext
- 类:
SuiteContext
- 测试运行器
-
► 索引
- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲区
- C++ 插件
- 使用 Node-API 的 C/C++ 插件
- C++ 嵌入器 API
- 子进程
- 集群
- 命令行选项
- 控制台
- Corepack
- 加密
- 调试器
- 已弃用的 API
- 诊断通道
- DNS
- 域
- 错误
- 事件
- 文件系统
- 全局变量
- HTTP
- HTTP/2
- HTTPS
- 检查器
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:包
- 网络
- 操作系统
- 路径
- 性能钩子
- 权限
- 进程
- Punycode
- 查询字符串
- 读取行
- 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');
仅测试#
如果使用--test-only
命令行选项启动 Node.js,则可以通过向应运行的测试传递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
在监视模式下,测试运行器将监视测试文件及其依赖项的更改。当检测到更改时,测试运行器将重新运行受更改影响的测试。测试运行器将继续运行,直到进程终止。
从命令行运行测试#
可以通过传递 --test
标志从命令行调用 Node.js 测试运行器
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
选项的值如何。
收集代码覆盖率#
当使用 --experimental-test-coverage
命令行标志启动 Node.js 时,将收集代码覆盖率,并在所有测试完成后报告统计信息。如果使用 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
对象支持在测试期间进行模拟。以下示例在将两个数字相加的函数上创建了一个 spy。然后使用 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.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 在对象方法上创建一个 spy。通过测试上下文进行模拟的好处是,测试运行器将在测试完成后自动恢复所有模拟功能。
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> 设置测试子进程的检查器端口。这可以是一个数字,或一个不带参数且返回数字的函数。如果提供了空值,则每个进程都会获取自己的端口,从主进程的process.debugPort
开始递增。默认值:undefined
。only
: <boolean> 如果为真,则测试上下文将只运行设置了only
选项的测试setup
<Function> 一个接受TestsStream
实例的函数,可用于在运行任何测试之前设置侦听器。默认值:undefined
。signal
<AbortSignal> 允许中止正在进行的测试执行。testNamePatterns
<string> | <RegExp> | <Array> 字符串、正则表达式或正则表达式数组,可用于仅运行名称与提供的模式匹配的测试。测试名称模式解释为 JavaScript 正则表达式。对于执行的每个测试,任何相应的测试挂钩(如beforeEach()
)也会运行。默认值:undefined
。timeout
<number> 测试执行失败后的毫秒数。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。watch
<boolean> 是否以监视模式运行。默认值:false
。shard
<Object> 在特定分片中运行测试。默认值:undefined
。
- 返回:<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
<string> 测试的名称,在报告测试结果时显示。默认值:fn
的name
属性,如果fn
没有名称,则为'<anonymous>'
。options
<Object> 测试的配置选项。支持以下属性concurrency
<number> | <boolean> 如果提供数字,则应用程序线程中将并行运行那么多测试。如果为true
,则所有已计划的异步测试都在线程中并发运行。如果为false
,则一次只运行一个测试。如果未指定,子测试将从其父级继承此值。默认值:false
。only
<布尔值> 如果为真,并且测试环境被配置为运行only
测试,那么将运行此测试。否则,将跳过该测试。默认值:false
。signal
<AbortSignal> 允许中止正在进行的测试。skip
<布尔值> | <字符串> 如果为真,则跳过测试。如果提供了字符串,则该字符串将作为跳过测试的原因显示在测试结果中。默认值:false
。todo
<布尔值> | <字符串> 如果为真,则将测试标记为TODO
。如果提供了字符串,则该字符串将作为测试为TODO
的原因显示在测试结果中。默认值:false
。timeout
<数字> 测试将在其后失败的毫秒数。如果未指定,则子测试将从其父级继承此值。默认值:Infinity
。
fn
<函数> | <异步函数> 正在测试的函数。此函数的第一个参数是TestContext
对象。如果测试使用回调,则将回调函数作为第二个参数传递。默认值:无操作函数。- 返回:<Promise> 测试完成后使用
undefined
兑现,或者如果测试在describe()
中运行,则立即兑现。
test()
函数是从 test
模块导入的值。此函数的每次调用都会将测试报告给 <TestsStream>。
传递给 fn
参数的 TestContext
对象可用于执行与当前测试相关的操作。示例包括跳过测试、添加其他诊断信息或创建子测试。
test()
返回一个 Promise
,该 Promise
在测试完成后兑现。如果在 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
#
一个获取器,返回用于跟踪对模拟调用的内部数组的副本。数组中的每个条目都是一个具有以下属性的对象。
arguments
<数组> 传递给模拟函数的参数数组。error
<any> 如果模拟函数抛出,则此属性包含抛出的值。默认值:undefined
。result
<any> 模拟函数返回的值。stack
<错误> 一个错误
对象,其堆栈可用于确定模拟函数调用的调用位置。target
<函数> | <undefined> 如果模拟函数是一个构造函数,则此字段包含正在构造的类。否则,这将是undefined
。this
<any> 模拟函数的this
值。
ctx.callCount()
#
- 返回: <整数> 此模拟被调用的次数。
此函数返回此模拟被调用的次数。此函数比检查ctx.calls.length
更有效,因为ctx.calls
是一个创建内部调用跟踪数组副本的获取器。
ctx.mockImplementation(implementation)
#
此函数用于更改现有模拟的行为。
以下示例使用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
的行为。此值必须大于零的整数。默认值: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])
#
此函数是 options.setter
设置为 true
的 MockTracker.method
的语法糖。
类: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
<number> | <Date> 表示要作为Date.now()
值使用的初始时间(以毫秒为单位)的可选数字或 Date 对象。默认值: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 });
将初始 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'
)。将模拟 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)
#
设置将用作任何模拟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()
将当前时间传递给模拟的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
<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> 表示行号和覆盖次数的行数组。
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:dequeue'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套级别。
在测试出列之前立即发出。
事件:'test:diagnostic'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。message
<string> 诊断消息。nesting
<number> 测试的嵌套级别。
在调用 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> 测试的嵌套级别。
在测试排队执行时发出。
事件:'test:fail'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。details
<Object> 其他执行元数据。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套级别。testNumber
<数字> 测试的序数。todo
<字符串> | <布尔值> | <未定义> 如果调用context.todo
则显示skip
<字符串> | <布尔值> | <未定义> 如果调用context.skip
则显示
当测试失败时发出。
事件:'test:pass'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。details
<Object> 其他执行元数据。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套级别。testNumber
<数字> 测试的序数。todo
<字符串> | <布尔值> | <未定义> 如果调用context.todo
则显示skip
<字符串> | <布尔值> | <未定义> 如果调用context.skip
则显示
当测试通过时发出。
事件:'test:plan'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。nesting
<number> 测试的嵌套级别。count
<数字> 已运行的子测试数。
当给定测试的所有子测试都已完成时发出。
事件:'test:start'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试通过 REPL 运行,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套级别。
当测试开始报告其自身及其子测试状态时发出。此事件保证按定义测试的顺序发出。
事件:'test:stderr'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<字符串> 测试文件路径。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。message
<字符串> 写入stderr
的消息。
当正在运行的测试写入stderr
时发出。仅当传递--test
标志时才会发出此事件。
事件:'test:stdout'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试通过 REPL 运行,则为undefined
。file
<字符串> 测试文件路径。line
<number> | <undefined> 定义测试的行号,如果测试通过 REPL 运行,则为undefined
。message
<string> 写入stdout
的消息。
当正在运行的测试写入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
选项的测试。否则,将运行所有测试。如果未使用--test-only
命令行选项启动 Node.js,则此函数将为 no-op。
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
<AbortSignal> 允许中止正在进行的测试。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
#
当测试已中止时,可用于中止测试子任务。