Node.js v25.0.0 文档
- Node.js v25.0.0
-
目录
- 测试运行器
- 子测试
- 跳过测试
- 重新运行失败的测试
- TODO 测试
describe()
和it()
别名only
测试- 按名称筛选测试
- 无关的异步活动
- 监视模式
- 全局设置和拆卸
- 从命令行运行测试
- 收集代码覆盖率
- 模拟 (Mocking)
- 快照测试
- 测试报告器
run([options])
suite([name][, options][, fn])
suite.skip([name][, options][, fn])
suite.todo([name][, options][, fn])
suite.only([name][, options][, fn])
test([name][, options][, fn])
test.skip([name][, options][, fn])
test.todo([name][, options][, fn])
test.only([name][, options][, fn])
describe([name][, options][, fn])
describe.skip([name][, options][, fn])
describe.todo([name][, options][, fn])
describe.only([name][, options][, fn])
it([name][, options][, fn])
it.skip([name][, options][, fn])
it.todo([name][, options][, fn])
it.only([name][, options][, fn])
before([fn][, options])
after([fn][, options])
beforeEach([fn][, options])
afterEach([fn][, options])
assert
snapshot
- 类:
MockFunctionContext
- 类:
MockModuleContext
- 类:
MockPropertyContext
- 类:
MockTracker
mock.fn([original[, implementation]][, options])
mock.getter(object, methodName[, implementation][, options])
mock.method(object, methodName[, implementation][, options])
mock.module(specifier[, options])
mock.property(object, propertyName[, value])
mock.reset()
mock.restoreAll()
mock.setter(object, methodName[, implementation][, options])
- 类:
MockTimers
- 类:
TestsStream
- 类:
TestContext
context.before([fn][, options])
context.beforeEach([fn][, options])
context.after([fn][, options])
context.afterEach([fn][, options])
context.assert
context.diagnostic(message)
context.filePath
context.fullName
context.name
context.plan(count[,options])
context.runOnly(shouldRunOnlyTests)
context.signal
context.skip([message])
context.todo([message])
context.test([name][, options][, fn])
context.waitFor(condition[, options])
- 类:
SuiteContext
- 测试运行器
-
索引
- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲区
- C++ 插件
- 使用 Node-API 的 C/C++ 插件
- C++ 嵌入器 API
- 子进程
- 集群
- 命令行选项
- 控制台
- 加密
- 调试器
- 已弃用的 API
- 诊断通道
- DNS
- 域
- 环境变量
- 错误
- 事件
- 文件系统
- 全局对象
- HTTP
- HTTP/2
- HTTPS
- 检查器
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:包
- 模块:TypeScript
- 网络
- 操作系统
- 路径
- 性能钩子
- 权限
- 进程
- Punycode
- 查询字符串
- 逐行读取
- REPL
- 报告
- 单一可执行文件应用
- SQLite
- 流
- 字符串解码器
- 测试运行器
- 定时器
- TLS/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用工具
- V8
- 虚拟机
- WASI
- Web Crypto API
- Web Streams API
- 工作线程
- Zlib
- 其他版本
- 选项
测试运行器#
源代码: lib/test.js
node:test
模块有助于创建 JavaScript 测试。要访问它:
import test from 'node:test';
const test = require('node:test');
此模块仅在 node:
协议下可用。
通过 test
模块创建的测试由一个函数组成,该函数通过以下三种方式之一进行处理:
- 一个同步函数,如果它抛出异常则被视为失败,否则被视为通过。
- 一个返回
Promise
的函数,如果Promise
被拒绝(reject),则被视为失败,如果Promise
被兑现(fulfill),则被视为通过。 - 一个接收回调函数的函数。如果回调函数收到的第一个参数是任何真值(truthy value),则测试被视为失败。如果传递给回调函数的第一个参数是假值(falsy value),则测试被视为通过。如果测试函数接收了一个回调函数并且还返回了一个
Promise
,则测试将失败。
以下示例说明了如何使用 test
模块编写测试。
test('synchronous passing test', (t) => {
// This test passes because it does not throw an exception.
assert.strictEqual(1, 1);
});
test('synchronous failing test', (t) => {
// This test fails because it throws an exception.
assert.strictEqual(1, 2);
});
test('asynchronous passing test', async (t) => {
// This test passes because the Promise returned by the async
// function is settled and not rejected.
assert.strictEqual(1, 1);
});
test('asynchronous failing test', async (t) => {
// This test fails because the Promise returned by the async
// function is rejected.
assert.strictEqual(1, 2);
});
test('failing test using Promises', (t) => {
// Promises can be used directly as well.
return new Promise((resolve, reject) => {
setImmediate(() => {
reject(new Error('this will cause the test to fail'));
});
});
});
test('callback passing test', (t, done) => {
// done() is the callback function. When the setImmediate() runs, it invokes
// done() with no arguments.
setImmediate(done);
});
test('callback failing test', (t, done) => {
// When the setImmediate() runs, done() is invoked with an Error object and
// the test fails.
setImmediate(() => {
done(new Error('callback failure'));
});
});
如果有任何测试失败,进程退出码将被设置为 1
。
子测试#
测试上下文的 test()
方法允许创建子测试。它允许您以分层方式组织测试,即可以在一个更大的测试中创建嵌套测试。此方法的行为与顶层 test()
函数完全相同。以下示例演示了创建一个带有两个子测试的顶层测试。
test('top level test', async (t) => {
await t.test('subtest 1', (t) => {
assert.strictEqual(1, 1);
});
await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2);
});
});
注意:
beforeEach
和afterEach
钩子(hooks)会在每个子测试执行之间触发。
在此示例中,使用了 await
来确保两个子测试都已完成。这是必需的,因为测试不会等待其子测试完成,这与在测试套件(suite)中创建的测试不同。任何在其父测试完成时仍未完成的子测试都将被取消并视为失败。任何子测试的失败都会导致父测试失败。
跳过测试#
可以通过向测试传递 skip
选项,或调用测试上下文的 skip()
方法来跳过单个测试,如下例所示。
// The skip option is used, but no message is provided.
test('skip option', { skip: true }, (t) => {
// This code is never executed.
});
// The skip option is used, and a message is provided.
test('skip option with message', { skip: 'this is skipped' }, (t) => {
// This code is never executed.
});
test('skip() method', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip();
});
test('skip() method with message', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip('this is skipped');
});
重新运行失败的测试#
测试运行器支持将运行状态持久化到文件中,从而允许测试运行器重新运行失败的测试,而无需重新运行整个测试套件。使用 --test-rerun-failures
命令行选项指定一个文件路径,用于存储运行状态。如果状态文件不存在,测试运行器将创建它。状态文件是一个 JSON 文件,其中包含一个运行尝试的数组。每次运行尝试都是一个对象,将成功的测试映射到它们通过的尝试次数。在此映射中,标识测试的键是测试文件路径,以及测试定义的行号和列号。在特定位置定义的测试被多次运行的情况下(例如在函数或循环中),将在键的末尾附加一个计数器,以消除测试运行的歧义。请注意,更改测试执行顺序或测试位置可能导致测试运行器认为测试已在先前的尝试中通过,这意味着 --test-rerun-failures
应该在测试以确定性顺序运行时使用。
状态文件示例:
[
{
"test.js:10:5": { "passed_on_attempt": 0, "name": "test 1" },
},
{
"test.js:10:5": { "passed_on_attempt": 0, "name": "test 1" },
"test.js:20:5": { "passed_on_attempt": 1, "name": "test 2" }
}
]
在此示例中,有两次运行尝试,在 test.js
中定义了两个测试,第一个测试在第一次尝试时成功,第二个测试在第二次尝试时成功。
当使用 --test-rerun-failures
选项时,测试运行器将只运行尚未通过的测试。
node --test-rerun-failures /path/to/state/file
TODO 测试#
可以通过向测试传递 todo
选项或调用测试上下文的 todo()
方法,将单个测试标记为不稳定的(flaky)或未完成的,如下例所示。这些测试代表待实现的功能或需要修复的错误。TODO 测试会被执行,但不会被视为测试失败,因此不会影响进程的退出码。如果一个测试同时被标记为 TODO 和跳过(skipped),则 TODO 选项将被忽略。
// The todo option is used, but no message is provided.
test('todo option', { todo: true }, (t) => {
// This code is executed, but not treated as a failure.
throw new Error('this does not fail the test');
});
// The todo option is used, and a message is provided.
test('todo option with message', { todo: 'this is a todo test' }, (t) => {
// This code is executed.
});
test('todo() method', (t) => {
t.todo();
});
test('todo() method with message', (t) => {
t.todo('this is a todo test and is not treated as a failure');
throw new Error('this does not fail the test');
});
describe()
和 it()
别名#
测试套件和测试也可以使用 describe()
和 it()
函数来编写。describe()
是 suite()
的别名,而 it()
是 test()
的别名。
describe('A thing', () => {
it('should work', () => {
assert.strictEqual(1, 1);
});
it('should be ok', () => {
assert.strictEqual(2, 2);
});
describe('a nested thing', () => {
it('should work', () => {
assert.strictEqual(3, 3);
});
});
});
describe()
和 it()
从 node:test
模块导入。
import { describe, it } from 'node:test';
const { describe, it } = require('node:test');
only
测试#
如果 Node.js 启动时带有 --test-only
命令行选项,或者测试隔离被禁用,可以通过向应运行的测试传递 only
选项来跳过除选定子集之外的所有测试。当设置了带有 only
选项的测试时,所有子测试也会运行。如果一个测试套件(suite)设置了 only
选项,则该套件内的所有测试都会运行,除非它有设置了 only
选项的后代,在这种情况下,只有那些测试会运行。
在 test()
/it()
中使用子测试时,需要将所有祖先测试都标记为 only
选项,才能仅运行选定的测试子集。
测试上下文的 runOnly()
方法可用于在子测试级别实现相同的行为。未执行的测试将从测试运行器的输出中省略。
// Assume Node.js is run with the --test-only command-line option.
// The suite's 'only' option is set, so these tests are run.
test('this test is run', { only: true }, async (t) => {
// Within this test, all subtests are run by default.
await t.test('running subtest');
// The test context can be updated to run subtests with the 'only' option.
t.runOnly(true);
await t.test('this subtest is now skipped');
await t.test('this subtest is run', { only: true });
// Switch the context back to execute all tests.
t.runOnly(false);
await t.test('this subtest is now run');
// Explicitly do not run these tests.
await t.test('skipped subtest 3', { only: false });
await t.test('skipped subtest 4', { skip: true });
});
// The 'only' option is not set, so this test is skipped.
test('this test is not run', () => {
// This code is not run.
throw new Error('fail');
});
describe('a suite', () => {
// The 'only' option is set, so this test is run.
it('this test is run', { only: true }, () => {
// This code is run.
});
it('this test is not run', () => {
// This code is not run.
throw new Error('fail');
});
});
describe.only('a suite', () => {
// The 'only' option is set, so this test is run.
it('this test is run', () => {
// This code is run.
});
it('this test is run', () => {
// This code is run.
});
});
按名称筛选测试#
--test-name-pattern
命令行选项可用于仅运行名称与所提供模式匹配的测试,而 --test-skip-pattern
选项可用于跳过名称与所提供模式匹配的测试。测试名称模式被解释为 JavaScript 正则表达式。--test-name-pattern
和 --test-skip-pattern
选项可以多次指定,以便运行嵌套测试。对于每个执行的测试,任何相应的测试钩子(如 beforeEach()
)也会运行。未执行的测试将从测试运行器的输出中省略。
给定以下测试文件,使用 --test-name-pattern="test [1-3]"
选项启动 Node.js 将导致测试运行器执行 test 1
、test 2
和 test 3
。如果 test 1
不匹配测试名称模式,那么它的子测试即使匹配模式也不会执行。同一组测试也可以通过多次传递 --test-name-pattern
来执行(例如 --test-name-pattern="test 1"
、--test-name-pattern="test 2"
等)。
test('test 1', async (t) => {
await t.test('test 2');
await t.test('test 3');
});
test('Test 4', async (t) => {
await t.test('Test 5');
await t.test('test 6');
});
测试名称模式也可以使用正则表达式字面量来指定。这允许使用正则表达式标志。在前面的示例中,使用 --test-name-pattern="/test [4-5]/i"
(或 --test-skip-pattern="/test [4-5]/i"
)启动 Node.js 将匹配 Test 4
和 Test 5
,因为该模式是大小写不敏感的。
要用模式匹配单个测试,您可以在其前面加上所有祖先测试的名称,并用空格分隔,以确保其唯一性。例如,给定以下测试文件:
describe('test 1', (t) => {
it('some test');
});
describe('test 2', (t) => {
it('some test');
});
使用 --test-name-pattern="test 1 some test"
启动 Node.js 将只匹配 test 1
中的 some test
。
测试名称模式不会改变测试运行器执行的文件集。
如果同时提供了 --test-name-pattern
和 --test-skip-pattern
,测试必须满足两个要求才能被执行。
无关的异步活动#
一旦测试函数执行完毕,结果会尽可能快地报告,同时保持测试的顺序。然而,测试函数可能会产生比测试本身生命周期更长的异步活动。测试运行器会处理这种类型的活动,但不会为了容纳它而延迟报告测试结果。
在以下示例中,一个测试完成时还有两个 setImmediate()
操作未完成。第一个 setImmediate()
尝试创建一个新的子测试。因为父测试已经完成并输出了其结果,所以新的子测试会立即被标记为失败,并稍后报告给 <TestsStream>。
第二个 setImmediate()
创建了一个 uncaughtException
事件。源自已完成测试的 uncaughtException
和 unhandledRejection
事件会被 test
模块标记为失败,并由 <TestsStream> 在顶层作为诊断警告报告。
test('a test that creates asynchronous activity', (t) => {
setImmediate(() => {
t.test('subtest that is created too late', (t) => {
throw new Error('error1');
});
});
setImmediate(() => {
throw new Error('error2');
});
// The test finishes after this line.
});
监视模式#
Node.js 测试运行器支持通过传递 --watch
标志在监视模式下运行:
node --test --watch
在监视模式下,测试运行器将监视测试文件及其依赖项的更改。当检测到更改时,测试运行器将重新运行受更改影响的测试。测试运行器将持续运行,直到进程被终止。
全局设置和拆卸#
测试运行器支持指定一个模块,该模块将在所有测试执行前被评估,可用于为测试设置全局状态或固定装置(fixtures)。这对于准备资源或设置多个测试所需的共享状态非常有用。
此模块可以导出以下任何一项:
- 一个
globalSetup
函数,它在所有测试开始前运行一次 - 一个
globalTeardown
函数,它在所有测试完成后运行一次
在从命令行运行测试时,使用 --test-global-setup
标志指定该模块。
// setup-module.js
async function globalSetup() {
// Setup shared resources, state, or environment
console.log('Global setup executed');
// Run servers, create files, prepare databases, etc.
}
async function globalTeardown() {
// Clean up resources, state, or environment
console.log('Global teardown executed');
// Close servers, remove files, disconnect from databases, etc.
}
module.exports = { globalSetup, globalTeardown };
// setup-module.mjs
export async function globalSetup() {
// Setup shared resources, state, or environment
console.log('Global setup executed');
// Run servers, create files, prepare databases, etc.
}
export async function globalTeardown() {
// Clean up resources, state, or environment
console.log('Global teardown executed');
// Close servers, remove files, disconnect from databases, etc.
}
如果全局设置函数抛出错误,则不会运行任何测试,并且进程将以非零退出码退出。在这种情况下,不会调用全局拆卸函数。
从命令行运行测试#
可以通过传递 --test
标志从命令行调用 Node.js 测试运行器:
node --test
默认情况下,Node.js 将运行所有匹配这些模式的文件:
**/*.test.{cjs,mjs,js}
**/*-test.{cjs,mjs,js}
**/*_test.{cjs,mjs,js}
**/test-*.{cjs,mjs,js}
**/test.{cjs,mjs,js}
**/test/**/*.{cjs,mjs,js}
除非提供了 --no-experimental-strip-types
,否则还会匹配以下附加模式:
**/*.test.{cts,mts,ts}
**/*-test.{cts,mts,ts}
**/*_test.{cts,mts,ts}
**/test-*.{cts,mts,ts}
**/test.{cts,mts,ts}
**/test/**/*.{cts,mts,ts}
或者,可以将一个或多个 glob 模式作为 Node.js 命令的最后参数提供,如下所示。Glob 模式遵循 glob(7)
的行为。Glob 模式应在命令行上用双引号括起来,以防止 shell 扩展,这会降低跨系统的可移植性。
node --test "**/*.test.js" "**/*.spec.js"
匹配的文件将作为测试文件执行。有关测试文件执行的更多信息,请参见测试运行器执行模型部分。
测试运行器执行模型#
当启用进程级测试隔离时,每个匹配的测试文件都在一个单独的子进程中执行。任何时候运行的最大子进程数由 --test-concurrency
标志控制。如果子进程以退出码 0 结束,则测试被视为通过。否则,测试被视为失败。测试文件必须可由 Node.js 执行,但不需要在内部使用 node:test
模块。
每个测试文件都像常规脚本一样执行。也就是说,如果测试文件本身使用 node:test
来定义测试,所有这些测试都将在单个应用程序线程中执行,而不管 test()
的 concurrency
选项的值如何。
当禁用进程级测试隔离时,每个匹配的测试文件都被导入到测试运行器进程中。一旦所有测试文件加载完毕,顶层测试将以并发数为一的方式执行。由于所有测试文件都在同一上下文中运行,测试之间可能会以启用隔离时不可能的方式相互作用。例如,如果一个测试依赖于全局状态,该状态可能会被来自另一个文件的测试修改。
子进程选项继承#
在进程隔离模式(默认)下运行测试时,生成的子进程会从父进程继承 Node.js 选项,包括在配置文件中指定的选项。但是,某些标志会被过滤掉以确保测试运行器的正常功能:
--test
- 阻止以避免递归测试执行--experimental-test-coverage
- 由测试运行器管理--watch
- 监视模式在父级处理--experimental-default-config-file
- 配置文件加载由父级处理--test-reporter
- 报告由父进程管理--test-reporter-destination
- 输出目标由父级控制--experimental-config-file
- 配置文件路径由父级管理
来自命令行参数、环境变量和配置文件的所有其他 Node.js 选项都由子进程继承。
收集代码覆盖率#
当 Node.js 启动时带有 --experimental-test-coverage
命令行标志时,会收集代码覆盖率,并在所有测试完成后报告统计信息。如果使用 NODE_V8_COVERAGE
环境变量指定代码覆盖率目录,则生成的 V8 覆盖率文件将写入该目录。Node.js 核心模块和 node_modules/
目录中的文件默认不包含在覆盖率报告中。但是,可以通过 --test-coverage-include
标志明确包含它们。默认情况下,所有匹配的测试文件都从覆盖率报告中排除。可以使用 --test-coverage-exclude
标志覆盖排除项。如果启用了覆盖率,覆盖率报告将通过 'test:coverage'
事件发送给任何测试报告器。
可以使用以下注释语法在一系列行上禁用覆盖率:
/* node:coverage disable */
if (anAlwaysFalseCondition) {
// Code in this branch will never be executed, but the lines are ignored for
// coverage purposes. All lines following the 'disable' comment are ignored
// until a corresponding 'enable' comment is encountered.
console.log('this is never executed');
}
/* node:coverage enable */
也可以为指定数量的行禁用覆盖率。在指定行数之后,覆盖率将自动重新启用。如果未明确提供行数,则忽略单行。
/* node:coverage ignore next */
if (anAlwaysFalseCondition) { console.log('this is never executed'); }
/* node:coverage ignore next 3 */
if (anAlwaysFalseCondition) {
console.log('this is never executed');
}
覆盖率报告器#
tap 和 spec 报告器将打印覆盖率统计摘要。还有一个 lcov 报告器,它将生成一个 lcov 文件,可用作深入的覆盖率报告。
node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info
- 此报告器不报告任何测试结果。
- 此报告器最好与另一个报告器一起使用。
模拟 (Mocking)#
node:test
模块支持在测试期间通过顶层 mock
对象进行模拟。以下示例为一个将两个数字相加的函数创建一个侦察器(spy)。然后使用该侦察器来断言函数是否按预期被调用。
import assert from 'node:assert';
import { mock, test } from 'node:test';
test('spies on a function', () => {
const sum = mock.fn((a, b) => {
return a + b;
});
assert.strictEqual(sum.mock.callCount(), 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.callCount(), 1);
const call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, 7);
assert.strictEqual(call.error, undefined);
// Reset the globally tracked mocks.
mock.reset();
});
'use strict';
const assert = require('node:assert');
const { mock, test } = require('node:test');
test('spies on a function', () => {
const sum = mock.fn((a, b) => {
return a + b;
});
assert.strictEqual(sum.mock.callCount(), 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.mock.callCount(), 1);
const call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, 7);
assert.strictEqual(call.error, undefined);
// Reset the globally tracked mocks.
mock.reset();
});
同样的模拟功能也暴露在每个测试的 TestContext
对象上。以下示例使用 TestContext
上暴露的 API 为一个对象方法创建一个侦察器。通过测试上下文进行模拟的好处是,测试运行器将在测试完成后自动恢复所有被模拟的功能。
test('spies on an object method', (t) => {
const number = {
value: 5,
add(a) {
return this.value + a;
},
};
t.mock.method(number, 'add');
assert.strictEqual(number.add.mock.callCount(), 0);
assert.strictEqual(number.add(3), 8);
assert.strictEqual(number.add.mock.callCount(), 1);
const call = number.add.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3]);
assert.strictEqual(call.result, 8);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, number);
});
定时器#
模拟定时器是软件测试中常用的一种技术,用于模拟和控制定时器(如 setInterval
和 setTimeout
)的行为,而无需实际等待指定的时间间隔。
有关方法和功能的完整列表,请参阅 MockTimers
类。
这使得开发者能够为依赖时间的功能编写更可靠、更可预测的测试。
下面的例子展示了如何模拟 setTimeout
。使用 .enable({ apis: ['setTimeout'] });
将会模拟 node:timers 和 node:timers/promises 模块以及 Node.js 全局上下文中的 setTimeout
函数。
注意: 此 API 目前不支持解构函数,例如 import { setTimeout } from 'node:timers'
。
import assert from 'node:assert';
import { mock, test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', () => {
const fn = mock.fn();
// Optionally choose what to mock
mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
// Reset the globally tracked mocks.
mock.timers.reset();
// If you call reset mock instance, it will also reset timers instance
mock.reset();
});
const assert = require('node:assert');
const { mock, test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', () => {
const fn = mock.fn();
// Optionally choose what to mock
mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
// Reset the globally tracked mocks.
mock.timers.reset();
// If you call reset mock instance, it will also reset timers instance
mock.reset();
});
同样的模拟功能也暴露在每个测试的 TestContext
对象的 mock 属性中。通过测试上下文进行模拟的好处是,测试运行器将在测试完成后自动恢复所有被模拟的定时器功能。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
日期#
模拟定时器 API 还允许模拟 Date
对象。这是一个有用的功能,用于测试依赖时间的功能,或模拟内部日历函数,如 Date.now()
。
日期的实现也是 MockTimers
类的一部分。有关方法和功能的完整列表,请参阅该类。
注意: 当日期和定时器一起被模拟时,它们是相互依赖的。这意味着,如果您同时模拟了 Date
和 setTimeout
,推进时间也会推进模拟的日期,因为它们模拟了一个单一的内部时钟。
下面的例子展示了如何模拟 Date
对象并获取当前的 Date.now()
值。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks the Date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'] });
// If not specified, the initial date will be based on 0 in the UNIX epoch
assert.strictEqual(Date.now(), 0);
// Advance in time will also advance the date
context.mock.timers.tick(9999);
assert.strictEqual(Date.now(), 9999);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks the Date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'] });
// If not specified, the initial date will be based on 0 in the UNIX epoch
assert.strictEqual(Date.now(), 0);
// Advance in time will also advance the date
context.mock.timers.tick(9999);
assert.strictEqual(Date.now(), 9999);
});
如果没有设置初始的纪元时间,初始日期将基于 Unix 纪元中的 0。即 1970 年 1 月 1 日 00:00:00 UTC。您可以通过向 .enable()
方法传递一个 now
属性来设置一个初始日期。该值将用作模拟 Date
对象的初始日期。它可以是一个正整数,也可以是另一个 Date 对象。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks the Date object with initial time', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 300);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks the Date object with initial time', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 300);
});
您可以使用 .setTime()
方法手动将模拟的日期移动到另一个时间。此方法只接受一个正整数。
注意: 此方法将执行所有在新时间之前应该触发的模拟定时器。
在下面的示例中,我们为模拟日期设置了一个新的时间。
import assert from 'node:assert';
import { test } from 'node:test';
test('sets the time of a date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.setTime(1000);
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 1200);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('sets the time of a date object', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// Advance in time will also advance the date
context.mock.timers.setTime(1000);
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 1200);
});
如果您有任何设置为在过去运行的定时器,它将被执行,就好像调用了 .tick()
方法一样。如果您想测试已经过去的依赖时间的功能,这将非常有用。
import assert from 'node:assert';
import { test } from 'node:test';
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
context.mock.timers.setTime(800);
// Timer is not executed as the time is not yet reached
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 800);
context.mock.timers.setTime(1200);
// Timer is executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 1200);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
context.mock.timers.setTime(800);
// Timer is not executed as the time is not yet reached
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 800);
context.mock.timers.setTime(1200);
// Timer is executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 1200);
});
使用 .runAll()
将执行当前队列中的所有定时器。这也会将模拟的日期推进到最后一个被执行的定时器的时间,就好像时间已经流逝了一样。
import assert from 'node:assert';
import { test } from 'node:test';
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
setTimeout(fn, 2000);
setTimeout(fn, 3000);
context.mock.timers.runAll();
// All timers are executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 3);
assert.strictEqual(Date.now(), 3000);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('runs timers as setTime passes ticks', (context) => {
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
setTimeout(fn, 2000);
setTimeout(fn, 3000);
context.mock.timers.runAll();
// All timers are executed as the time is now reached
assert.strictEqual(fn.mock.callCount(), 3);
assert.strictEqual(Date.now(), 3000);
});
快照测试#
快照测试允许将任意值序列化为字符串值,并与一组已知良好值进行比较。已知良好值被称为快照,并存储在快照文件中。快照文件由测试运行器管理,但设计为人类可读,以帮助调试。最佳实践是将快照文件与测试文件一起检入源代码控制。
快照文件通过使用 --test-update-snapshots
命令行标志启动 Node.js 来生成。为每个测试文件生成一个单独的快照文件。默认情况下,快照文件的名称与测试文件相同,但文件扩展名为 .snapshot
。可以使用 snapshot.setResolveSnapshotPath()
函数配置此行为。每个快照断言对应于快照文件中的一个导出项。
下面显示了一个快照测试的示例。第一次执行此测试时,它会失败,因为相应的快照文件不存在。
// test.js
suite('suite of snapshot tests', () => {
test('snapshot test', (t) => {
t.assert.snapshot({ value1: 1, value2: 2 });
t.assert.snapshot(5);
});
});
通过使用 --test-update-snapshots
运行测试文件来生成快照文件。测试应该通过,并在与测试文件相同的目录中创建一个名为 test.js.snapshot
的文件。快照文件的内容如下所示。每个快照都由测试的全名和一个计数器来标识,以区分同一测试中的快照。
exports[`suite of snapshot tests > snapshot test 1`] = `
{
"value1": 1,
"value2": 2
}
`;
exports[`suite of snapshot tests > snapshot test 2`] = `
5
`;
快照文件创建后,再次运行测试,但不要带 --test-update-snapshots
标志。现在测试应该会通过。
测试报告器#
node:test
模块支持传递 --test-reporter
标志,以使测试运行器使用特定的报告器。
支持以下内置报告器:
-
spec
spec
报告器以人类可读的格式输出测试结果。这是默认报告器。 -
tap
tap
报告器以 TAP 格式输出测试结果。 -
dot
dot
报告器以紧凑格式输出测试结果,其中每个通过的测试由一个.
表示,每个失败的测试由一个X
表示。 -
junit
junit 报告器以 jUnit XML 格式输出测试结果。 -
lcov
当与--experimental-test-coverage
标志一起使用时,lcov
报告器输出测试覆盖率。
这些报告器的确切输出可能会在 Node.js 版本之间发生变化,不应在程序中依赖它。如果需要以编程方式访问测试运行器的输出,请使用由 <TestsStream> 发出的事件。
报告器可通过 node:test/reporters
模块获得。
import { tap, spec, dot, junit, lcov } from 'node:test/reporters';
const { tap, spec, dot, junit, lcov } = require('node:test/reporters');
自定义报告器#
--test-reporter
可用于指定自定义报告器的路径。自定义报告器是一个模块,它导出一个被 stream.compose 接受的值。报告器应转换由 <TestsStream> 发出的事件。
使用 <stream.Transform> 的自定义报告器示例:
import { Transform } from 'node:stream';
const customReporter = new Transform({
writableObjectMode: true,
transform(event, encoding, callback) {
switch (event.type) {
case 'test:dequeue':
callback(null, `test ${event.data.name} dequeued`);
break;
case 'test:enqueue':
callback(null, `test ${event.data.name} enqueued`);
break;
case 'test:watch:drained':
callback(null, 'test watch queue drained');
break;
case 'test:watch:restarted':
callback(null, 'test watch restarted due to file change');
break;
case 'test:start':
callback(null, `test ${event.data.name} started`);
break;
case 'test:pass':
callback(null, `test ${event.data.name} passed`);
break;
case 'test:fail':
callback(null, `test ${event.data.name} failed`);
break;
case 'test:plan':
callback(null, 'test plan');
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
callback(null, event.data.message);
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
callback(null, `total line count: ${totalLineCount}\n`);
break;
}
}
},
});
export default customReporter;
const { Transform } = require('node:stream');
const customReporter = new Transform({
writableObjectMode: true,
transform(event, encoding, callback) {
switch (event.type) {
case 'test:dequeue':
callback(null, `test ${event.data.name} dequeued`);
break;
case 'test:enqueue':
callback(null, `test ${event.data.name} enqueued`);
break;
case 'test:watch:drained':
callback(null, 'test watch queue drained');
break;
case 'test:watch:restarted':
callback(null, 'test watch restarted due to file change');
break;
case 'test:start':
callback(null, `test ${event.data.name} started`);
break;
case 'test:pass':
callback(null, `test ${event.data.name} passed`);
break;
case 'test:fail':
callback(null, `test ${event.data.name} failed`);
break;
case 'test:plan':
callback(null, 'test plan');
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
callback(null, event.data.message);
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
callback(null, `total line count: ${totalLineCount}\n`);
break;
}
}
},
});
module.exports = customReporter;
使用生成器函数的自定义报告器示例:
export default async function * customReporter(source) {
for await (const event of source) {
switch (event.type) {
case 'test:dequeue':
yield `test ${event.data.name} dequeued\n`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued\n`;
break;
case 'test:watch:drained':
yield 'test watch queue drained\n';
break;
case 'test:watch:restarted':
yield 'test watch restarted due to file change\n';
break;
case 'test:start':
yield `test ${event.data.name} started\n`;
break;
case 'test:pass':
yield `test ${event.data.name} passed\n`;
break;
case 'test:fail':
yield `test ${event.data.name} failed\n`;
break;
case 'test:plan':
yield 'test plan\n';
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
yield `${event.data.message}\n`;
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
yield `total line count: ${totalLineCount}\n`;
break;
}
}
}
}
module.exports = async function * customReporter(source) {
for await (const event of source) {
switch (event.type) {
case 'test:dequeue':
yield `test ${event.data.name} dequeued\n`;
break;
case 'test:enqueue':
yield `test ${event.data.name} enqueued\n`;
break;
case 'test:watch:drained':
yield 'test watch queue drained\n';
break;
case 'test:watch:restarted':
yield 'test watch restarted due to file change\n';
break;
case 'test:start':
yield `test ${event.data.name} started\n`;
break;
case 'test:pass':
yield `test ${event.data.name} passed\n`;
break;
case 'test:fail':
yield `test ${event.data.name} failed\n`;
break;
case 'test:plan':
yield 'test plan\n';
break;
case 'test:diagnostic':
case 'test:stderr':
case 'test:stdout':
yield `${event.data.message}\n`;
break;
case 'test:coverage': {
const { totalLineCount } = event.data.summary.totals;
yield `total line count: ${totalLineCount}\n`;
break;
}
}
}
};
提供给 --test-reporter
的值应该是一个字符串,就像在 JavaScript 代码的 import()
中使用的那样,或者是为 --import
提供的值。
多个报告器#
--test-reporter
标志可以多次指定,以多种格式报告测试结果。在这种情况下,需要使用 --test-reporter-destination
为每个报告器指定一个目标。目标可以是 stdout
、stderr
或文件路径。报告器和目标根据它们指定的顺序进行配对。
在以下示例中,spec
报告器将输出到 stdout
,而 dot
报告器将输出到 file.txt
:
node --test-reporter=spec --test-reporter=dot --test-reporter-destination=stdout --test-reporter-destination=file.txt
当指定单个报告器时,目标将默认为 stdout
,除非明确提供了目标。
run([options])
#
options
<Object> 运行测试的配置选项。支持以下属性:concurrency
<number> | <boolean> 如果提供一个数字,则该数量的测试进程将并行运行,每个进程对应一个测试文件。如果为true
,它将并行运行os.availableParallelism() - 1
个测试文件。如果为false
,它将一次只运行一个测试文件。默认值:false
。cwd
<string> 指定测试运行器要使用的当前工作目录。作为解析文件的基本路径,就像从该目录从命令行运行测试一样。默认值:process.cwd()
。files
<Array> 一个包含要运行的文件列表的数组。默认值: 与从命令行运行测试相同。forceExit
<boolean> 配置测试运行器在所有已知测试执行完毕后退出进程,即使事件循环原本会保持活动状态。默认值:false
。globPatterns
<Array> 一个包含用于匹配测试文件的 glob 模式列表的数组。此选项不能与files
一起使用。默认值: 与从命令行运行测试相同。inspectPort
<number> | <Function> 设置测试子进程的检查器端口。这可以是一个数字,或一个不带参数并返回数字的函数。如果提供了一个空值(nullish value),每个进程将获得自己的端口,从主进程的process.debugPort
开始递增。如果isolation
选项设置为'none'
,则此选项被忽略,因为不会生成子进程。默认值:undefined
。isolation
<string> 配置测试隔离的类型。如果设置为'process'
,每个测试文件都在一个单独的子进程中运行。如果设置为'none'
,所有测试文件都在当前进程中运行。默认值:'process'
。only
<boolean> 如果为真值,测试上下文将只运行设置了only
选项的测试。setup
<Function> 一个接受TestsStream
实例的函数,可用于在任何测试运行之前设置监听器。默认值:undefined
。execArgv
<Array> 一个在生成子进程时传递给node
可执行文件的 CLI 标志数组。当isolation
为'none'
时,此选项无效。默认值:[]
argv
<Array> 一个在生成子进程时传递给每个测试文件的 CLI 标志数组。当isolation
为'none'
时,此选项无效。默认值:[]
。signal
<AbortSignal> 允许中止正在进行的测试执行。testNamePatterns
<string> | <RegExp> | <Array> 一个字符串、RegExp 或 RegExp 数组,可用于仅运行名称与所提供模式匹配的测试。测试名称模式被解释为 JavaScript 正则表达式。对于每个执行的测试,任何相应的测试钩子,如beforeEach()
,也会运行。默认值:undefined
。testSkipPatterns
<string> | <RegExp> | <Array> 一个字符串、RegExp 或 RegExp 数组,可用于排除运行名称与所提供模式匹配的测试。测试名称模式被解释为 JavaScript 正则表达式。对于每个执行的测试,任何相应的测试钩子,如beforeEach()
,也会运行。默认值:undefined
。timeout
<number> 测试执行将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。watch
<boolean> 是否以监视模式运行。默认值:false
。shard
<Object> 在特定分片中运行测试。默认值:undefined
。rerunFailuresFilePath
<string> 一个文件路径,测试运行器将在其中存储测试状态,以便在下次运行时仅重新运行失败的测试。有关更多信息,请参阅[重新运行失败的测试][]。默认值:undefined
。coverage
<boolean> 启用代码覆盖率收集。默认值:false
。coverageExcludeGlobs
<string> | <Array> 使用 glob 模式从代码覆盖率中排除特定文件,该模式可以匹配绝对和相对文件路径。此属性仅在coverage
设置为true
时适用。如果同时提供了coverageExcludeGlobs
和coverageIncludeGlobs
,文件必须满足两个条件才能被包含在覆盖率报告中。默认值:undefined
。coverageIncludeGlobs
<string> | <Array> 使用 glob 模式在代码覆盖率中包含特定文件,该模式可以匹配绝对和相对文件路径。此属性仅在coverage
设置为true
时适用。如果同时提供了coverageExcludeGlobs
和coverageIncludeGlobs
,文件必须满足两个条件才能被包含在覆盖率报告中。默认值:undefined
。lineCoverage
<number> 要求覆盖行的最低百分比。如果代码覆盖率未达到指定的阈值,进程将以代码1
退出。默认值:0
。branchCoverage
<number> 要求覆盖分支的最低百分比。如果代码覆盖率未达到指定的阈值,进程将以代码1
退出。默认值:0
。functionCoverage
<number> 要求覆盖函数的最低百分比。如果代码覆盖率未达到指定的阈值,进程将以代码1
退出。默认值:0
。
- 返回:<TestsStream>
注意: shard
用于在多台机器或多个进程之间水平并行化测试运行,非常适合在不同环境中进行大规模执行。它与 watch
模式不兼容,后者专为快速代码迭代而设计,通过在文件更改时自动重新运行测试。
import { tap } from 'node:test/reporters';
import { run } from 'node:test';
import process from 'node:process';
import path from 'node:path';
run({ files: [path.resolve('./tests/test.js')] })
.on('test:fail', () => {
process.exitCode = 1;
})
.compose(tap)
.pipe(process.stdout);
const { tap } = require('node:test/reporters');
const { run } = require('node:test');
const path = require('node:path');
run({ files: [path.resolve('./tests/test.js')] })
.on('test:fail', () => {
process.exitCode = 1;
})
.compose(tap)
.pipe(process.stdout);
suite([name][, options][, fn])
#
name
<string> 测试套件的名称,在报告测试结果时显示。默认值:fn
的name
属性,如果fn
没有名称,则为'<anonymous>'
。options
<Object> 测试套件的可选配置选项。它支持与test([name][, options][, fn])
相同的选项。fn
<Function> | <AsyncFunction> 声明嵌套测试和套件的套件函数。此函数的第一个参数是SuiteContext
对象。默认值: 一个空操作函数。- 返回:<Promise> 立即以
undefined
兑现。
suite()
函数从 node:test
模块导入。
suite.skip([name][, options][, fn])
#
跳过测试套件的简写。这与 suite([name], { skip: true }[, fn])
相同。
suite.todo([name][, options][, fn])
#
将测试套件标记为 TODO
的简写。这与 suite([name], { todo: true }[, fn])
相同。
suite.only([name][, options][, fn])
#
将测试套件标记为 only
的简写。这与 suite([name], { only: true }[, fn])
相同。
test([name][, options][, fn])
#
name
<string> 测试的名称,在报告测试结果时显示。默认值:fn
的name
属性,如果fn
没有名称,则为'<anonymous>'
。options
<Object> 测试的配置选项。支持以下属性:concurrency
<number> | <boolean> 如果提供一个数字,则该数量的测试将在应用程序线程内并行运行。如果为true
,所有计划的异步测试将在线程内并发运行。如果为false
,一次只运行一个测试。如果未指定,子测试将从其父级继承此值。默认值:false
。only
<boolean> 如果为真值,并且测试上下文配置为只运行only
测试,则此测试将被运行。否则,该测试将被跳过。默认值:false
。signal
<AbortSignal> 允许中止正在进行的测试。skip
<boolean> | <string> 如果为真值,则跳过该测试。如果提供一个字符串,该字符串将作为跳过测试的原因显示在测试结果中。默认值:false
。todo
<boolean> | <string> 如果为真值,则将测试标记为TODO
。如果提供一个字符串,该字符串将作为测试为TODO
的原因显示在测试结果中。默认值:false
。timeout
<number> 测试将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。plan
<number> 预计在测试中运行的断言和子测试的数量。如果测试中运行的断言数量与计划中指定的数量不匹配,测试将失败。默认值:undefined
。
fn
<Function> | <AsyncFunction> 被测试的函数。此函数的第一个参数是TestContext
对象。如果测试使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。- 返回:<Promise> 一旦测试完成,将以
undefined
兑现,或者如果测试在套件内运行,则立即兑现。
test()
函数是从 test
模块导入的值。每次调用此函数都会导致向 <TestsStream> 报告测试。
传递给 fn
参数的 TestContext
对象可用于执行与当前测试相关的操作。例如,跳过测试、添加额外的诊断信息或创建子测试。
test()
返回一个在测试完成后兑现的 Promise
。如果 test()
在套件内调用,它会立即兑现。对于顶层测试,返回值通常可以被丢弃。然而,应使用子测试的返回值来防止父测试先完成并取消子测试,如下例所示。
test('top level test', async (t) => {
// The setTimeout() in the following subtest would cause it to outlive its
// parent test if 'await' is removed on the next line. Once the parent test
// completes, it will cancel any outstanding subtests.
await t.test('longer running subtest', async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});
timeout
选项可用于在测试完成时间超过 timeout
毫秒时使测试失败。然而,它不是一个可靠的取消测试机制,因为一个正在运行的测试可能会阻塞应用程序线程,从而阻止预定的取消操作。
test.skip([name][, options][, fn])
#
跳过测试的简写,与 test([name], { skip: true }[, fn])
相同。
test.todo([name][, options][, fn])
#
将测试标记为 TODO
的简写,与 test([name], { todo: true }[, fn])
相同。
test.only([name][, options][, fn])
#
将测试标记为 only
的简写,与 test([name], { only: true }[, fn])
相同。
describe([name][, options][, fn])
#
suite()
的别名。
describe()
函数从 node:test
模块导入。
describe.skip([name][, options][, fn])
#
跳过测试套件的简写。这与 describe([name], { skip: true }[, fn])
相同。
describe.todo([name][, options][, fn])
#
将测试套件标记为 TODO
的简写。这与 describe([name], { todo: true }[, fn])
相同。
describe.only([name][, options][, fn])
#
将测试套件标记为 only
的简写。这与 describe([name], { only: true }[, fn])
相同。
it([name][, options][, fn])
#
test()
的别名。
it()
函数从 node:test
模块导入。
it.skip([name][, options][, fn])
#
跳过测试的简写,与 it([name], { skip: true }[, fn])
相同。
it.todo([name][, options][, fn])
#
将测试标记为 TODO
的简写,与 it([name], { todo: true }[, fn])
相同。
it.only([name][, options][, fn])
#
将测试标记为 only
的简写,与 it([name], { only: true }[, fn])
相同。
before([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。如果钩子使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数创建一个在执行套件之前运行的钩子。
describe('tests', async () => {
before(() => console.log('about to run some test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
after([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。如果钩子使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数创建一个在执行套件之后运行的钩子。
describe('tests', async () => {
after(() => console.log('finished running tests'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
注意: after
钩子保证会运行,即使套件内的测试失败。
beforeEach([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。如果钩子使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数创建一个在当前套件中每个测试之前运行的钩子。
describe('tests', async () => {
beforeEach(() => console.log('about to run a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
afterEach([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。如果钩子使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数创建一个在当前套件中每个测试之后运行的钩子。即使测试失败,afterEach()
钩子也会运行。
describe('tests', async () => {
afterEach(() => console.log('finished running a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
assert
#
一个对象,其方法用于在当前进程中配置 TestContext
对象上可用的断言。默认情况下,来自 node:assert
的方法和快照测试函数是可用的。
通过将通用配置代码放在使用 --require
或 --import
预加载的模块中,可以将相同的配置应用于所有文件。
assert.register(name, fn)
#
使用提供的名称和函数定义一个新的断言函数。如果已存在同名的断言,它将被覆盖。
snapshot
#
一个对象,其方法用于在当前进程中配置默认的快照设置。通过将通用配置代码放在使用 --require
或 --import
预加载的模块中,可以将相同的配置应用于所有文件。
snapshot.setDefaultSnapshotSerializers(serializers)
#
serializers
<Array> 一个同步函数数组,用作快照测试的默认序列化器。
此函数用于自定义测试运行器使用的默认序列化机制。默认情况下,测试运行器通过在提供的值上调用 JSON.stringify(value, null, 2)
来执行序列化。JSON.stringify()
在处理循环结构和支持的数据类型方面存在限制。如果需要更强大的序列化机制,应使用此函数。
snapshot.setResolveSnapshotPath(fn)
#
fn
<Function> 一个用于计算快照文件位置的函数。该函数接收测试文件的路径作为其唯一参数。如果测试与文件无关(例如在 REPL 中),则输入为 undefined。fn()
必须返回一个指定快照文件位置的字符串。
此函数用于自定义快照测试所使用的快照文件的位置。默认情况下,快照文件名与入口点文件名相同,但文件扩展名为 .snapshot
。
类:MockFunctionContext
#
MockFunctionContext
类用于检查或操作通过 MockTracker
API 创建的模拟(mocks)的行为。
ctx.calls
#
- 类型:<Array>
一个 getter,返回用于跟踪对模拟函数调用的内部数组的副本。数组中的每个条目都是一个具有以下属性的对象。
arguments
<Array> 一个包含传递给模拟函数的参数的数组。error
<any> 如果被模拟的函数抛出异常,则此属性包含抛出的值。默认值:undefined
。result
<any> 被模拟函数返回的值。stack
<Error> 一个Error
对象,其堆栈可用于确定被模拟函数调用的位置。target
<Function> | <undefined> 如果被模拟的函数是构造函数,此字段包含正在构造的类。否则此字段为undefined
。this
<any> 被模拟函数的this
值。
ctx.callCount()
#
- 返回:<integer> 此模拟被调用的次数。
此函数返回此模拟被调用的次数。此函数比检查 ctx.calls.length
更高效,因为 ctx.calls
是一个创建内部调用跟踪数组副本的 getter。
ctx.mockImplementation(implementation)
#
implementation
<Function> | <AsyncFunction> 用作模拟新实现的函数。
此函数用于更改现有模拟的行为。
以下示例使用 t.mock.fn()
创建一个模拟函数,调用该模拟函数,然后将模拟实现更改为另一个函数。
test('changes a mock behavior', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne);
assert.strictEqual(fn(), 1);
fn.mock.mockImplementation(addTwo);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn(), 5);
});
ctx.mockImplementationOnce(implementation[, onCall])
#
implementation
<Function> | <AsyncFunction> 用于在onCall
指定的调用次数时作为模拟实现的函数。onCall
<integer> 将使用implementation
的调用次数。如果指定的调用已经发生,则抛出异常。默认值: 下一次调用的次数。
此函数用于在单次调用中更改现有模拟的行为。一旦第 onCall
次调用发生,模拟将恢复到未调用 mockImplementationOnce()
时的行为。
以下示例使用 t.mock.fn()
创建一个模拟函数,调用该模拟函数,在下一次调用时将模拟实现更改为另一个函数,然后恢复其先前的行为。
test('changes a mock behavior once', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne);
assert.strictEqual(fn(), 1);
fn.mock.mockImplementationOnce(addTwo);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn(), 4);
});
ctx.resetCalls()
#
重置模拟函数的调用历史记录。
ctx.restore()
#
将模拟函数的实现重置为其原始行为。调用此函数后,模拟仍然可以使用。
类:MockModuleContext
#
MockModuleContext
类用于操作通过 MockTracker
API 创建的模块模拟的行为。
ctx.restore()
#
重置模拟模块的实现。
类:MockPropertyContext
#
MockPropertyContext
类用于检查或操作通过 MockTracker
API 创建的属性模拟的行为。
ctx.accessCount()
#
- 返回:<integer> 属性被访问(读取或写入)的次数。
此函数返回属性被访问的次数。此函数比检查 ctx.accesses.length
更高效,因为 ctx.accesses
是一个创建内部访问跟踪数组副本的 getter。
ctx.mockImplementationOnce(value[, onAccess])
#
value
<any> 用于在由onAccess
指定的调用次数时作为模拟实现的值。onAccess
<integer> 将使用value
的调用次数。如果指定的调用已经发生,则会抛出异常。默认值: 下一次调用的次数。
此函数用于在单次调用中更改现有模拟的行为。一旦第 onAccess
次调用发生,模拟将恢复到未调用 mockImplementationOnce()
时的行为。
以下示例使用 t.mock.property()
创建一个模拟函数,调用该模拟属性,在下一次调用时将模拟实现更改为不同的值,然后恢复其先前的行为。
test('changes a mock behavior once', (t) => {
const obj = { foo: 1 };
const prop = t.mock.property(obj, 'foo', 5);
assert.strictEqual(obj.foo, 5);
prop.mock.mockImplementationOnce(25);
assert.strictEqual(obj.foo, 25);
assert.strictEqual(obj.foo, 5);
});
注意事项#
为了与模拟 API 的其余部分保持一致,此函数将属性的 get 和 set 都视为访问。如果属性 set 发生在相同的访问索引上,则“一次性”值将被 set 操作消耗,并且模拟属性值将更改为“一次性”值。如果您希望“一次性”值仅用于 get 操作,这可能会导致意外行为。
ctx.resetAccesses()
#
重置模拟属性的访问历史记录。
ctx.restore()
#
将模拟属性的实现重置为其原始行为。调用此函数后,模拟仍然可以使用。
类:MockTracker
#
MockTracker
类用于管理模拟功能。测试运行器模块提供了一个顶层 mock
导出,它是一个 MockTracker
实例。每个测试也通过其测试上下文的 mock
属性提供自己的 MockTracker
实例。
mock.fn([original[, implementation]][, options])
#
original
<Function> | <AsyncFunction> 一个可选的函数,在其上创建模拟。默认值: 一个空操作函数。implementation
<Function> | <AsyncFunction> 一个可选的函数,用作original
的模拟实现。这对于创建在指定调用次数内表现出一种行为,然后恢复original
行为的模拟很有用。默认值:original
指定的函数。options
<Object> 模拟函数的可选配置选项。支持以下属性:times
<integer> 模拟将使用implementation
行为的次数。一旦模拟函数被调用了times
次,它将自动恢复original
的行为。此值必须是大于零的整数。默认值:Infinity
。
- 返回:<Proxy> 被模拟的函数。被模拟的函数包含一个特殊的
mock
属性,它是MockFunctionContext
的实例,可用于检查和更改被模拟函数的行为。
此函数用于创建模拟函数。
以下示例创建一个模拟函数,每次调用时将计数器加一。times
选项用于修改模拟行为,使得前两次调用将计数器加二而不是一。
test('mocks a counting function', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
const fn = t.mock.fn(addOne, addTwo, { times: 2 });
assert.strictEqual(fn(), 2);
assert.strictEqual(fn(), 4);
assert.strictEqual(fn(), 5);
assert.strictEqual(fn(), 6);
});
mock.getter(object, methodName[, implementation][, options])
#
此函数是 MockTracker.method
的语法糖,其中 options.getter
设置为 true
。
mock.method(object, methodName[, implementation][, options])
#
object
<Object> 其方法被模拟的对象。methodName
<string> | <symbol>object
上要模拟的方法的标识符。如果object[methodName]
不是函数,则抛出错误。implementation
<Function> | <AsyncFunction> 一个可选的函数,用作object[methodName]
的模拟实现。默认值:object[methodName]
指定的原始方法。options
<Object> 模拟方法的可选配置选项。支持以下属性:- 返回:<Proxy> 被 mock 的方法。被 mock 的方法包含一个特殊的
mock
属性,它是MockFunctionContext
的实例,可用于检查和更改被 mock 方法的行为。
此函数用于在现有对象方法上创建 mock。以下示例演示了如何在现有对象方法上创建 mock。
test('spies on an object method', (t) => {
const number = {
value: 5,
subtract(a) {
return this.value - a;
},
};
t.mock.method(number, 'subtract');
assert.strictEqual(number.subtract.mock.callCount(), 0);
assert.strictEqual(number.subtract(3), 2);
assert.strictEqual(number.subtract.mock.callCount(), 1);
const call = number.subtract.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3]);
assert.strictEqual(call.result, 2);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, number);
});
mock.module(specifier[, options])
#
specifier
<string> | <URL> 标识要 mock 的模块的字符串。options
<Object> mock 模块的可选配置选项。支持以下属性:cache
<boolean> 如果为false
,每次调用require()
或import()
都会生成一个新的 mock 模块。如果为true
,后续调用将返回相同的模块 mock,并且该 mock 模块会插入到 CommonJS 缓存中。默认值:false
。defaultExport
<any> 一个可选值,用作 mock 模块的默认导出。如果未提供此值,ESM mock 不包含默认导出。如果 mock 是 CommonJS 或内置模块,此设置将用作module.exports
的值。如果未提供此值,CJS 和内置 mock 将使用一个空对象作为module.exports
的值。namedExports
<Object> 一个可选对象,其键和值用于创建 mock 模块的命名导出。如果 mock 是 CommonJS 或内置模块,这些值将被复制到module.exports
上。因此,如果创建的 mock 同时具有命名导出和非对象的默认导出,当作为 CJS 或内置模块使用时,该 mock 将抛出异常。
- 返回:<MockModuleContext> 一个可用于操作 mock 的对象。
此函数用于 mock ECMAScript 模块、CommonJS 模块、JSON 模块和 Node.js 内置模块的导出。在 mock 之前对原始模块的任何引用都不会受到影响。为了启用模块 mock,Node.js 必须使用 --experimental-test-module-mocks
命令行标志启动。
以下示例演示了如何为模块创建 mock。
test('mocks a builtin module in both module systems', async (t) => {
// Create a mock of 'node:readline' with a named export named 'fn', which
// does not exist in the original 'node:readline' module.
const mock = t.mock.module('node:readline', {
namedExports: { fn() { return 42; } },
});
let esmImpl = await import('node:readline');
let cjsImpl = require('node:readline');
// cursorTo() is an export of the original 'node:readline' module.
assert.strictEqual(esmImpl.cursorTo, undefined);
assert.strictEqual(cjsImpl.cursorTo, undefined);
assert.strictEqual(esmImpl.fn(), 42);
assert.strictEqual(cjsImpl.fn(), 42);
mock.restore();
// The mock is restored, so the original builtin module is returned.
esmImpl = await import('node:readline');
cjsImpl = require('node:readline');
assert.strictEqual(typeof esmImpl.cursorTo, 'function');
assert.strictEqual(typeof cjsImpl.cursorTo, 'function');
assert.strictEqual(esmImpl.fn, undefined);
assert.strictEqual(cjsImpl.fn, undefined);
});
mock.property(object, propertyName[, value])
#
object
<Object> 其值被 mock 的对象。propertyName
<string> | <symbol>object
上要 mock 的属性的标识符。value
<any> 一个可选值,用作object[propertyName]
的 mock 值。默认值: 原始属性值。- 返回:<Proxy> 指向被 mock 对象的代理。被 mock 的对象包含一个特殊的
mock
属性,它是MockPropertyContext
的实例,可用于检查和更改被 mock 属性的行为。
为一个对象上的属性值创建 mock。这允许你跟踪和控制对特定属性的访问,包括它被读取(getter)或写入(setter)的次数,并在 mock 后恢复原始值。
test('mocks a property value', (t) => {
const obj = { foo: 42 };
const prop = t.mock.property(obj, 'foo', 100);
assert.strictEqual(obj.foo, 100);
assert.strictEqual(prop.mock.accessCount(), 1);
assert.strictEqual(prop.mock.accesses[0].type, 'get');
assert.strictEqual(prop.mock.accesses[0].value, 100);
obj.foo = 200;
assert.strictEqual(prop.mock.accessCount(), 2);
assert.strictEqual(prop.mock.accesses[1].type, 'set');
assert.strictEqual(prop.mock.accesses[1].value, 200);
prop.mock.restore();
assert.strictEqual(obj.foo, 42);
});
mock.reset()
#
此函数恢复所有先前由此 MockTracker
创建的 mock 的默认行为,并将这些 mock 与 MockTracker
实例解除关联。一旦解除关联,这些 mock 仍然可以使用,但 MockTracker
实例不能再用于重置它们的行为或以其他方式与它们交互。
在每个测试完成后,此函数会在测试上下文的 MockTracker
上被调用。如果全局 MockTracker
被广泛使用,建议手动调用此函数。
mock.restoreAll()
#
此函数恢复所有先前由此 MockTracker
创建的 mock 的默认行为。与 mock.reset()
不同,mock.restoreAll()
不会将 mock 与 MockTracker
实例解除关联。
mock.setter(object, methodName[, implementation][, options])
#
此函数是 MockTracker.method
的语法糖,其中 options.setter
设置为 true
。
类:MockTimers
#
模拟定时器是软件测试中常用的一种技术,用于模拟和控制定时器(如 setInterval
和 setTimeout
)的行为,而无需实际等待指定的时间间隔。
MockTimers 也能够 mock Date
对象。
MockTracker
提供一个顶层 timers
导出,它是一个 MockTimers
实例。
timers.enable([enableOptions])
#
为指定的计时器启用 mock 功能。
enableOptions
<Object> 启用计时器 mock 的可选配置选项。支持以下属性:apis
<Array> 一个包含要 mock 的计时器的可选数组。当前支持的计时器值为'setInterval'
、'setTimeout'
、'setImmediate'
和'Date'
。默认值:['setInterval', 'setTimeout', 'setImmediate', 'Date']
。如果未提供数组,默认将 mock 所有与时间相关的 API('setInterval'
、'clearInterval'
、'setTimeout'
、'clearTimeout'
、'setImmediate'
、'clearImmediate'
和'Date'
)。now
<number> | <Date> 一个可选的数字或 Date 对象,表示用作Date.now()
值的初始时间(以毫秒为单位)。默认值:0
。
注意: 当你为特定计时器启用 mock 时,其关联的清除函数也将被隐式 mock。
注意: mock Date
会影响被 mock 计时器的行为,因为它们使用相同的内部时钟。
不设置初始时间的使用示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['setInterval'] });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['setInterval'] });
上述示例为 setInterval
计时器启用 mock,并隐式 mock 了 clearInterval
函数。只有来自 node:timers、node:timers/promises 和 globalThis
的 setInterval
和 clearInterval
函数会被 mock。
设置初始时间的使用示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: 1000 });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: 1000 });
使用初始 Date 对象作为时间设置的示例
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: new Date() });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: new Date() });
或者,如果你不带任何参数调用 mock.timers.enable()
所有计时器('setInterval'
、'clearInterval'
、'setTimeout'
、'clearTimeout'
、'setImmediate'
和 'clearImmediate'
)都将被 mock。来自 node:timers
、node:timers/promises
和 globalThis
的 setInterval
、clearInterval
、setTimeout
、clearTimeout
、setImmediate
和 clearImmediate
函数将被 mock。以及全局 Date
对象。
timers.reset()
#
此函数恢复所有先前由此 MockTimers
实例创建的 mock 的默认行为,并将这些 mock 与 MockTracker
实例解除关联。
注意: 在每个测试完成后,此函数会在测试上下文的 MockTracker
上被调用。
import { mock } from 'node:test';
mock.timers.reset();
const { mock } = require('node:test');
mock.timers.reset();
timers[Symbol.dispose]()
#
调用 timers.reset()
。
timers.tick([milliseconds])
#
为所有被 mock 的计时器推进时间。
milliseconds
<number> 推进计时器的时间量,以毫秒为单位。默认值:1
。
注意: 这与 Node.js 中 setTimeout
的行为不同,只接受正数。在 Node.js 中,支持带有负数的 setTimeout
仅是为了 web 兼容性。
以下示例 mock 了一个 setTimeout
函数,并使用 .tick
来推进时间,触发所有待处理的计时器。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
});
另外,.tick
函数可以被多次调用
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
const nineSecs = 9000;
setTimeout(fn, nineSecs);
const threeSeconds = 3000;
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
assert.strictEqual(fn.mock.callCount(), 1);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout'] });
const nineSecs = 9000;
setTimeout(fn, nineSecs);
const threeSeconds = 3000;
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
context.mock.timers.tick(threeSeconds);
assert.strictEqual(fn.mock.callCount(), 1);
});
使用 .tick
推进时间也会推进在 mock 启用后创建的任何 Date
对象的时间(如果 Date
也被设置为 mock)。
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 9999);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 0);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 9999);
});
使用清除函数#
如前所述,计时器中的所有清除函数(clearTimeout
、clearInterval
和 clearImmediate
)都会被隐式 mock。请看这个使用 setTimeout
的例子
import assert from 'node:assert';
import { test } from 'node:test';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
const id = setTimeout(fn, 9999);
// Implicitly mocked as well
clearTimeout(id);
context.mock.timers.tick(9999);
// As that setTimeout was cleared the mock function will never be called
assert.strictEqual(fn.mock.callCount(), 0);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
const fn = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
const id = setTimeout(fn, 9999);
// Implicitly mocked as well
clearTimeout(id);
context.mock.timers.tick(9999);
// As that setTimeout was cleared the mock function will never be called
assert.strictEqual(fn.mock.callCount(), 0);
});
使用 Node.js 计时器模块#
一旦你启用了计时器 mock,node:timers、node:timers/promises 模块以及来自 Node.js 全局上下文的计时器都将被启用
注意: 此 API 目前不支持解构函数,例如 import { setTimeout } from 'node:timers'
。
import assert from 'node:assert';
import { test } from 'node:test';
import nodeTimers from 'node:timers';
import nodeTimersPromises from 'node:timers/promises';
test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => {
const globalTimeoutObjectSpy = context.mock.fn();
const nodeTimerSpy = context.mock.fn();
const nodeTimerPromiseSpy = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(globalTimeoutObjectSpy, 9999);
nodeTimers.setTimeout(nodeTimerSpy, 9999);
const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1);
assert.strictEqual(nodeTimerSpy.mock.callCount(), 1);
await promise;
assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1);
});
const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimers = require('node:timers');
const nodeTimersPromises = require('node:timers/promises');
test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => {
const globalTimeoutObjectSpy = context.mock.fn();
const nodeTimerSpy = context.mock.fn();
const nodeTimerPromiseSpy = context.mock.fn();
// Optionally choose what to mock
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(globalTimeoutObjectSpy, 9999);
nodeTimers.setTimeout(nodeTimerSpy, 9999);
const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy);
// Advance in time
context.mock.timers.tick(9999);
assert.strictEqual(globalTimeoutObjectSpy.mock.callCount(), 1);
assert.strictEqual(nodeTimerSpy.mock.callCount(), 1);
await promise;
assert.strictEqual(nodeTimerPromiseSpy.mock.callCount(), 1);
});
在 Node.js 中,来自 node:timers/promises 的 setInterval
是一个 AsyncGenerator
,此 API 也支持它
import assert from 'node:assert';
import { test } from 'node:test';
import nodeTimersPromises from 'node:timers/promises';
test('should tick five times testing a real use case', async (context) => {
context.mock.timers.enable({ apis: ['setInterval'] });
const expectedIterations = 3;
const interval = 1000;
const startedAt = Date.now();
async function run() {
const times = [];
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
times.push(time);
if (times.length === expectedIterations) break;
}
return times;
}
const r = run();
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
const timeResults = await r;
assert.strictEqual(timeResults.length, expectedIterations);
for (let it = 1; it < expectedIterations; it++) {
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
}
});
const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimersPromises = require('node:timers/promises');
test('should tick five times testing a real use case', async (context) => {
context.mock.timers.enable({ apis: ['setInterval'] });
const expectedIterations = 3;
const interval = 1000;
const startedAt = Date.now();
async function run() {
const times = [];
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
times.push(time);
if (times.length === expectedIterations) break;
}
return times;
}
const r = run();
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
context.mock.timers.tick(interval);
const timeResults = await r;
assert.strictEqual(timeResults.length, expectedIterations);
for (let it = 1; it < expectedIterations; it++) {
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
}
});
timers.runAll()
#
立即触发所有待处理的 mock 计时器。如果 Date
对象也被 mock,它也会将 Date
对象推进到最远计时器的时间。
下面的示例立即触发所有待处理的计时器,使它们无需任何延迟地执行。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
// Notice that if both timers have the same timeout,
// the order of execution is guaranteed
setTimeout(() => results.push(3), 8888);
setTimeout(() => results.push(2), 8888);
assert.deepStrictEqual(results, []);
context.mock.timers.runAll();
assert.deepStrictEqual(results, [3, 2, 1]);
// The Date object is also advanced to the furthest timer's time
assert.strictEqual(Date.now(), 9999);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
// Notice that if both timers have the same timeout,
// the order of execution is guaranteed
setTimeout(() => results.push(3), 8888);
setTimeout(() => results.push(2), 8888);
assert.deepStrictEqual(results, []);
context.mock.timers.runAll();
assert.deepStrictEqual(results, [3, 2, 1]);
// The Date object is also advanced to the furthest timer's time
assert.strictEqual(Date.now(), 9999);
});
注意: runAll()
函数是专门为在计时器 mock 上下文中触发计时器而设计的。它对实时系统时钟或 mock 环境之外的实际计时器没有任何影响。
timers.setTime(milliseconds)
#
设置将用作任何被 mock 的 Date
对象的参考的当前 Unix 时间戳。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
const now = Date.now();
const setTime = 1000;
// Date.now is not mocked
assert.deepStrictEqual(Date.now(), now);
context.mock.timers.enable({ apis: ['Date'] });
context.mock.timers.setTime(setTime);
// Date.now is now 1000
assert.strictEqual(Date.now(), setTime);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('setTime replaces current time', (context) => {
const now = Date.now();
const setTime = 1000;
// Date.now is not mocked
assert.deepStrictEqual(Date.now(), now);
context.mock.timers.enable({ apis: ['Date'] });
context.mock.timers.setTime(setTime);
// Date.now is now 1000
assert.strictEqual(Date.now(), setTime);
});
日期和计时器协同工作#
日期和计时器对象是相互依赖的。如果你使用 setTime()
将当前时间传递给被 mock 的 Date
对象,用 setTimeout
和 setInterval
设置的计时器将不会受到影响。
然而,tick
方法将会推进被 mock 的 Date
对象。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
assert.deepStrictEqual(results, []);
context.mock.timers.setTime(12000);
assert.deepStrictEqual(results, []);
// The date is advanced but the timers don't tick
assert.strictEqual(Date.now(), 12000);
});
const assert = require('node:assert');
const { test } = require('node:test');
test('runAll functions following the given order', (context) => {
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const results = [];
setTimeout(() => results.push(1), 9999);
assert.deepStrictEqual(results, []);
context.mock.timers.setTime(12000);
assert.deepStrictEqual(results, []);
// The date is advanced but the timers don't tick
assert.strictEqual(Date.now(), 12000);
});
类:TestsStream
#
- 继承自 <Readable>
成功调用 run()
方法将返回一个新的 <TestsStream> 对象,它会流式传输一系列表示测试执行的事件。TestsStream
将按照测试定义的顺序发出事件。
一些事件保证按照测试定义的相同顺序发出,而其他事件则按照测试执行的顺序发出。
事件:'test:coverage'
#
data
<Object>summary
<Object> 包含覆盖率报告的对象。files
<Array> 单个文件覆盖率报告的数组。每个报告都是一个具有以下结构的对象:path
<string> 文件的绝对路径。totalLineCount
<number> 总行数。totalBranchCount
<number> 总分支数。totalFunctionCount
<number> 总函数数。coveredLineCount
<number> 覆盖的行数。coveredBranchCount
<number> 覆盖的分支数。coveredFunctionCount
<number> 覆盖的函数数。coveredLinePercent
<number> 行覆盖率百分比。coveredBranchPercent
<number> 分支覆盖率百分比。coveredFunctionPercent
<number> 函数覆盖率百分比。functions
<Array> 表示函数覆盖率的函数数组。branches
<Array> 表示分支覆盖率的分支数组。lines
<Array> 表示行号及其被覆盖次数的行数组。
thresholds
<Object> 包含每种覆盖类型是否达标的对象。totals
<Object> 包含所有文件覆盖率摘要的对象。totalLineCount
<number> 总行数。totalBranchCount
<number> 总分支数。totalFunctionCount
<number> 总函数数。coveredLineCount
<number> 覆盖的行数。coveredBranchCount
<number> 覆盖的分支数。coveredFunctionCount
<number> 覆盖的函数数。coveredLinePercent
<number> 行覆盖率百分比。coveredBranchPercent
<number> 分支覆盖率百分比。coveredFunctionPercent
<number> 函数覆盖率百分比。
workingDirectory
<string> 代码覆盖率开始时的工作目录。这对于在测试更改了 Node.js 进程工作目录的情况下显示相对路径名很有用。
nesting
<number> 测试的嵌套层级。
当代码覆盖率启用且所有测试完成后发出。
事件:'test:complete'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。details
<Object> 附加的执行元数据。passed
<boolean> 测试是否通过。duration_ms
<number> 测试的持续时间(以毫秒为单位)。error
<Error> | <undefined> 如果测试未通过,则为一个包装了测试抛出的错误的错误对象。cause
<Error> 测试实际抛出的错误。
type
<string> | <undefined> 测试的类型,用于表示这是否是一个测试套件。
file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。testNumber
<number> 测试的序数。todo
<string> | <boolean> | <undefined> 如果调用了context.todo
,则存在此字段。skip
<string> | <boolean> | <undefined> 如果调用了context.skip
,则存在此字段。
当一个测试完成其执行时发出。此事件不是按照测试定义的顺序发出的。相应的按声明顺序的事件是 'test:pass'
和 'test:fail'
。
事件:'test:dequeue'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。type
<string> 测试类型。可以是'suite'
或'test'
。
当一个测试出队,即将执行之前发出。此事件不保证按照测试定义的顺序发出。相应的按声明顺序的事件是 'test:start'
。
事件:'test:diagnostic'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。message
<string> 诊断信息。nesting
<number> 测试的嵌套层级。level
<string> 诊断信息的严重性级别。可能的值有:'info'
:信息性消息。'warn'
:警告。'error'
:错误。
当调用 context.diagnostic
时发出。此事件保证按照测试定义的顺序发出。
事件:'test:enqueue'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。type
<string> 测试类型。可以是'suite'
或'test'
。
当一个测试被加入执行队列时发出。
事件:'test:fail'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。details
<Object> 附加的执行元数据。duration_ms
<number> 测试的持续时间(以毫秒为单位)。error
<Error> 一个包装了测试抛出的错误的错误对象。cause
<Error> 测试实际抛出的错误。
type
<string> | <undefined> 测试的类型,用于表示这是否是一个测试套件。attempt
<number> | <undefined> 测试运行的尝试次数,仅在使用--test-rerun-failures
标志时存在。
file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。testNumber
<number> 测试的序数。todo
<string> | <boolean> | <undefined> 如果调用了context.todo
,则存在此字段。skip
<string> | <boolean> | <undefined> 如果调用了context.skip
,则存在此字段。
当一个测试失败时发出。此事件保证按照测试定义的顺序发出。相应的按执行顺序的事件是 'test:complete'
。
事件:'test:pass'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。details
<Object> 附加的执行元数据。duration_ms
<number> 测试的持续时间(以毫秒为单位)。type
<string> | <undefined> 测试的类型,用于表示这是否是一个测试套件。attempt
<number> | <undefined> 测试运行的尝试次数,仅在使用--test-rerun-failures
标志时存在。passed_on_attempt
<number> | <undefined> 测试通过的尝试次数,仅在使用--test-rerun-failures
标志时存在。
file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。testNumber
<number> 测试的序数。todo
<string> | <boolean> | <undefined> 如果调用了context.todo
,则存在此字段。skip
<string> | <boolean> | <undefined> 如果调用了context.skip
,则存在此字段。
当一个测试通过时发出。此事件保证按照测试定义的顺序发出。相应的按执行顺序的事件是 'test:complete'
。
事件:'test:plan'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。nesting
<number> 测试的嵌套层级。count
<number> 已运行的子测试数量。
当给定测试的所有子测试都完成时发出。此事件保证按照测试定义的顺序发出。
事件:'test:start'
#
data
<Object>column
<number> | <undefined> 定义测试的列号,如果测试是通过 REPL 运行的,则为undefined
。file
<string> | <undefined> 测试文件的路径,如果测试是通过 REPL 运行的,则为undefined
。line
<number> | <undefined> 定义测试的行号,如果测试是通过 REPL 运行的,则为undefined
。name
<string> 测试名称。nesting
<number> 测试的嵌套层级。
当一个测试开始报告其自身及其子测试的状态时发出。此事件保证按照测试定义的顺序发出。相应的按执行顺序的事件是 'test:dequeue'
。
事件:'test:stderr'
#
当正在运行的测试写入 stderr
时发出。此事件仅在传递了 --test
标志时发出。此事件不保证按照测试定义的顺序发出。
事件:'test:stdout'
#
当正在运行的测试写入 stdout
时发出。此事件仅在传递了 --test
标志时发出。此事件不保证按照测试定义的顺序发出。
事件:'test:summary'
#
data
<Object>counts
<Object> 包含各种测试结果计数的对象。duration_ms
<number> 测试运行的持续时间(以毫秒为单位)。file
<string> | <undefined> 生成摘要的测试文件的路径。如果摘要对应多个文件,则此值为undefined
。success
<boolean> 指示测试运行是否被视为成功。如果发生任何错误情况,例如测试失败或未满足覆盖率阈值,此值将设置为false
。
当测试运行完成时发出。此事件包含与已完成测试运行相关的指标,对于确定测试运行是否通过或失败很有用。如果使用进程级测试隔离,除了最终的累积摘要外,还会为每个测试文件生成一个 'test:summary'
事件。
事件:'test:watch:drained'
#
在监视模式下,当没有更多测试排队等待执行时发出。
事件:'test:watch:restarted'
#
在监视模式下,由于文件更改而重新启动一个或多个测试时发出。
类:TestContext
#
TestContext
的实例会传递给每个测试函数,以便与测试运行器交互。但是,TestContext
构造函数并未作为 API 的一部分公开。
context.before([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。此函数的第一个参数是TestContext
对象。如果钩子使用回调,回调函数将作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数用于创建一个在当前测试的子测试之前运行的钩子。
context.beforeEach([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。此函数的第一个参数是TestContext
对象。如果钩子使用回调,回调函数将作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数用于创建一个在当前测试的每个子测试之前运行的钩子。
test('top level test', async (t) => {
t.beforeEach((t) => t.diagnostic(`about to run ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
},
);
});
context.after([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。此函数的第一个参数是TestContext
对象。如果钩子使用回调,回调函数将作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数用于创建一个在当前测试完成后运行的钩子。
test('top level test', async (t) => {
t.after((t) => t.diagnostic(`finished running ${t.name}`));
assert.ok('some relevant assertion here');
});
context.afterEach([fn][, options])
#
fn
<Function> | <AsyncFunction> 钩子函数。此函数的第一个参数是TestContext
对象。如果钩子使用回调,回调函数将作为第二个参数传递。默认值: 一个空操作函数。options
<Object> 钩子的配置选项。支持以下属性:signal
<AbortSignal> 允许中止正在进行的钩子。timeout
<number> 钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。
此函数用于创建一个在当前测试的每个子测试之后运行的钩子。
test('top level test', async (t) => {
t.afterEach((t) => t.diagnostic(`finished running ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
},
);
});
context.assert
#
一个包含绑定到 context
的断言方法的对象。node:assert
模块的顶层函数在这里公开,用于创建测试计划。
test('test', (t) => {
t.plan(1);
t.assert.strictEqual(true, true);
});
context.assert.fileSnapshot(value, path[, options])
#
value
<any> 要序列化为字符串的值。如果 Node.js 是用--test-update-snapshots
标志启动的,序列化的值将被写入path
。否则,序列化的值将与现有快照文件的内容进行比较。path
<string> 序列化的value
被写入的文件。options
<Object> 可选的配置选项。支持以下属性:serializers
<Array> 用于将value
序列化为字符串的同步函数数组。value
作为唯一参数传递给第一个序列化函数。每个序列化器的返回值将作为输入传递给下一个序列化器。所有序列化器运行完毕后,最终结果将被强制转换为字符串。默认值: 如果未提供序列化器,则使用测试运行器的默认序列化器。
此函数序列化 value
并将其写入由 path
指定的文件。
test('snapshot test with default serialization', (t) => {
t.assert.fileSnapshot({ value1: 1, value2: 2 }, './snapshots/snapshot.json');
});
此函数与 context.assert.snapshot()
在以下方面有所不同:
- 快照文件路径由用户明确提供。
- 每个快照文件仅限于一个快照值。
- 测试运行器不会执行额外的转义。
这些差异使得快照文件能够更好地支持语法高亮等功能。
context.assert.snapshot(value[, options])
#
value
<any> 要序列化为字符串的值。如果 Node.js 是用--test-update-snapshots
标志启动的,序列化的值将被写入快照文件。否则,序列化的值将与现有快照文件中相应的值进行比较。options
<Object> 可选的配置选项。支持以下属性:serializers
<Array> 用于将value
序列化为字符串的同步函数数组。value
作为唯一参数传递给第一个序列化函数。每个序列化器的返回值将作为输入传递给下一个序列化器。所有序列化器运行完毕后,最终结果将被强制转换为字符串。默认值: 如果未提供序列化器,则使用测试运行器的默认序列化器。
此函数实现了快照测试的断言。
test('snapshot test with default serialization', (t) => {
t.assert.snapshot({ value1: 1, value2: 2 });
});
test('snapshot test with custom serialization', (t) => {
t.assert.snapshot({ value3: 3, value4: 4 }, {
serializers: [(value) => JSON.stringify(value)],
});
});
context.diagnostic(message)
#
message
<string> 要报告的消息。
此函数用于将诊断信息写入输出。任何诊断信息都包含在测试结果的末尾。此函数不返回值。
test('top level test', (t) => {
t.diagnostic('A diagnostic message');
});
context.filePath
#
创建当前测试的测试文件的绝对路径。如果一个测试文件导入了其他生成测试的模块,导入的测试将返回根测试文件的路径。
context.fullName
#
测试的名称及其每个祖先的名称,用 >
分隔。
context.name
#
测试的名称。
context.plan(count[,options])
#
此函数用于设置测试中预期运行的断言和子测试的数量。如果运行的断言和子测试数量与预期数量不匹配,测试将失败。
注意:为确保断言被跟踪,必须使用
t.assert
而不是直接使用assert
。
test('top level test', (t) => {
t.plan(2);
t.assert.ok('some relevant assertion here');
t.test('subtest', () => {});
});
在处理异步代码时,plan
函数可用于确保运行正确数量的断言:
test('planning with streams', (t, done) => {
function* generate() {
yield 'a';
yield 'b';
yield 'c';
}
const expected = ['a', 'b', 'c'];
t.plan(expected.length);
const stream = Readable.from(generate());
stream.on('data', (chunk) => {
t.assert.strictEqual(chunk, expected.shift());
});
stream.on('end', () => {
done();
});
});
使用 wait
选项时,你可以控制测试等待预期断言的时间。例如,设置最大等待时间可以确保测试在指定的时间范围内等待异步断言完成:
test('plan with wait: 2000 waits for async assertions', (t) => {
t.plan(1, { wait: 2000 }); // Waits for up to 2 seconds for the assertion to complete.
const asyncActivity = () => {
setTimeout(() => {
t.assert.ok(true, 'Async assertion completed within the wait time');
}, 1000); // Completes after 1 second, within the 2-second wait time.
};
asyncActivity(); // The test will pass because the assertion is completed in time.
});
注意:如果指定了 wait
超时,它仅在测试函数执行完毕后才开始倒计时。
context.runOnly(shouldRunOnlyTests)
#
shouldRunOnlyTests
<boolean> 是否只运行带有only
选项的测试。
如果 shouldRunOnlyTests
为真值,测试上下文将只运行设置了 only
选项的测试。否则,所有测试都将运行。如果 Node.js 不是用 --test-only
命令行选项启动的,则此函数为空操作。
test('top level test', (t) => {
// The test context can be set to run subtests with the 'only' option.
t.runOnly(true);
return Promise.all([
t.test('this subtest is now skipped'),
t.test('this subtest is run', { only: true }),
]);
});
context.signal
#
可用于在测试被中止时中止测试子任务。
test('top level test', async (t) => {
await fetch('some/uri', { signal: t.signal });
});
context.skip([message])
#
message
<string> 可选的跳过消息。
此函数使测试的输出指示该测试被跳过。如果提供了 message
,它将包含在输出中。调用 skip()
不会终止测试函数的执行。此函数不返回值。
test('top level test', (t) => {
// Make sure to return here as well if the test contains additional logic.
t.skip('this is skipped');
});
context.todo([message])
#
message
<string> 可选的TODO
消息。
此函数向测试的输出添加一个 TODO
指令。如果提供了 message
,它将包含在输出中。调用 todo()
不会终止测试函数的执行。此函数不返回值。
test('top level test', (t) => {
// This test is marked as `TODO`
t.todo('this is a todo');
});
context.test([name][, options][, fn])
#
name
<string> 子测试的名称,在报告测试结果时显示。默认值:fn
的name
属性,如果fn
没有名称,则为'<anonymous>'
。options
<Object> 子测试的配置选项。支持以下属性:concurrency
<number> | <boolean> | <null> 如果提供一个数字,则在应用程序线程内将并行运行那么多数量的测试。如果为true
,将并行运行所有子测试。如果为false
,一次只运行一个测试。如果未指定,子测试将从其父测试继承此值。默认值:null
。only
<boolean> 如果为真值,并且测试上下文配置为只运行only
测试,则此测试将被运行。否则,该测试将被跳过。默认值:false
。signal
<AbortSignal> 允许中止正在进行的测试。skip
<boolean> | <string> 如果为真值,则跳过该测试。如果提供一个字符串,该字符串将作为跳过测试的原因显示在测试结果中。默认值:false
。todo
<boolean> | <string> 如果为真值,则将测试标记为TODO
。如果提供一个字符串,该字符串将作为测试为TODO
的原因显示在测试结果中。默认值:false
。timeout
<number> 测试将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。默认值:Infinity
。plan
<number> 预计在测试中运行的断言和子测试的数量。如果测试中运行的断言数量与计划中指定的数量不匹配,测试将失败。默认值:undefined
。
fn
<Function> | <AsyncFunction> 被测试的函数。此函数的第一个参数是TestContext
对象。如果测试使用回调,则回调函数作为第二个参数传递。默认值: 一个空操作函数。- 返回:<Promise> 在测试完成后以
undefined
兑现。
此函数用于在当前测试下创建子测试。此函数的行为与顶层 test()
函数相同。
test('top level test', async (t) => {
await t.test(
'This is a subtest',
{ only: false, skip: false, concurrency: 1, todo: false, plan: 1 },
(t) => {
t.assert.ok('some relevant assertion here');
},
);
});
context.waitFor(condition[, options])
#
condition
<Function> | <AsyncFunction> 一个断言函数,它会周期性地被调用,直到它成功完成或定义的轮询超时。成功完成定义为不抛出错误或拒绝。此函数不接受任何参数,并允许返回任何值。options
<Object> 轮询操作的可选配置对象。支持以下属性:- 返回:<Promise> 以
condition
返回的值兑现。
此方法轮询一个 condition
函数,直到该函数成功返回或操作超时。
类:SuiteContext
#
SuiteContext
的实例会传递给每个测试套件函数,以便与测试运行器交互。但是,SuiteContext
构造函数并未作为 API 的一部分公开。
context.filePath
#
创建当前测试套件的测试文件的绝对路径。如果一个测试文件导入了其他生成测试套件的模块,导入的套件将返回根测试文件的路径。
context.name
#
测试套件的名称。