测试运行器
History
稳定性:2 - 稳定
node:test 模块有助于创建 JavaScript 测试。
要访问它:
import test from 'node:test';此模块仅在 node: 方案下可用。
通过 test 模块创建的测试由单个函数组成,该函数通过以下三种方式之一进行处理:
- 一个同步函数,如果抛出异常则视为失败,否则视为通过。
- 一个返回
Promise的函数,如果Promise被拒绝则视为失败,如果Promise被履行则视为通过。 - 一个接收回调函数的函数。如果回调接收任何真值作为其第一个参数,则测试视为失败。如果将假值作为第一个参数传递给回调,则测试视为通过。如果测试函数接收回调函数并且还返回
Promise,则测试将失败。
以下示例说明了如何使用 test 模块编写测试。
test('synchronous passing test', (t) => {
// 此测试通过,因为它没有抛出异常。
assert.strictEqual(1, 1);
});
test('synchronous failing test', (t) => {
// 此测试失败,因为它抛出了异常。
assert.strictEqual(1, 2);
});
test('asynchronous passing test', async (t) => {
// 此测试通过,因为 async 函数返回的 Promise 已结算且未被拒绝。
assert.strictEqual(1, 1);
});
test('asynchronous failing test', async (t) => {
// 此测试失败,因为 async 函数返回的 Promise 被拒绝。
assert.strictEqual(1, 2);
});
test('failing test using Promises', (t) => {
// Promise 也可以直接使用。
return new Promise((resolve, reject) => {
setImmediate(() => {
reject(new Error('this will cause the test to fail'));
});
});
});
test('callback passing test', (t, done) => {
// done() 是回调函数。当 setImmediate() 运行时,它调用
// done() 不带参数。
setImmediate(done);
});
test('callback failing test', (t, done) => {
// 当 setImmediate() 运行时,done() 被传入一个 Error 对象且
// 测试失败。
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 来确保两个子测试都已完成。
这是必要的,因为测试不会等待其子测试
完成,这与在套件内创建的测试不同。
当父测试完成时任何仍未完成的子测试
将被取消并视为失败。任何子测试失败都会导致父
测试失败。
测试运行器支持将运行状态持久化到文件,允许
测试运行器重新运行失败的测试,而不必重新运行整个测试套件。
使用 --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套件和测试也可以使用 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';可以通过向测试传递 skip 选项,或通过
调用测试上下文的 skip() 方法来跳过单个测试,如下
所示示例。
// 使用了 skip 选项,但未提供消息。
test('skip option', { skip: true }, (t) => {
// 此代码永远不会执行。
});
// 使用了 skip 选项,并提供了消息。
test('skip option with message', { skip: 'this is skipped' }, (t) => {
// 此代码永远不会执行。
});
test('skip() method', (t) => {
// 如果测试包含额外逻辑,也请确保在此处返回。
t.skip();
});
test('skip() method with message', (t) => {
// 如果测试包含额外逻辑,也请确保在此处返回。
t.skip('this is skipped');
});可以通过向测试传递 todo
选项,或通过调用测试上下文的 todo() 方法,将单个测试标记为不稳定或未完成,如下
所示示例。这些测试代表待实现的实现或需要修复的
错误。TODO 测试会被执行,但不被视为测试
失败,因此不会影响进程退出代码。如果测试被标记
为 TODO 和跳过,则 TODO 选项将被忽略。
// 使用了 todo 选项,但未提供消息。
test('todo option', { todo: true }, (t) => {
// 此代码会被执行,但不被视为失败。
throw new Error('this does not fail the test');
});
// 使用了 todo 选项,并提供了消息。
test('todo option with message', { todo: 'this is a todo test' }, (t) => {
// 此代码会被执行。
});
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');
});预期测试失败
History
这会翻转特定测试或套件的通过/失败报告:标记的测试 用例必须抛出异常才能通过,而标记的未抛出异常的测试用例 则失败。
在以下每种情况中,doTheThing() 未能返回 true,但由于
测试被标记为 expectFailure,它们会通过。
it.expectFailure('should do the thing', () => {
assert.strictEqual(doTheThing(), true);
});
it('should do the thing', { expectFailure: true }, () => {
assert.strictEqual(doTheThing(), true);
});
it('should do the thing', { expectFailure: 'feature not implemented' }, () => {
assert.strictEqual(doTheThing(), true);
});如果 expectFailure 的值是 <RegExp> | <Function> | <Object> | <Error>
则仅当测试抛出匹配的值时才会通过。
请参阅 assert.throws 了解如何处理每种值类型。
以下每个测试都失败了,尽管 被标记为 expectFailure
因为失败不匹配特定的 预期 失败。
it('fails because regex does not match', {
expectFailure: /expected message/,
}, () => {
throw new Error('different message');
});
it('fails because object matcher does not match', {
expectFailure: { code: 'ERR_EXPECTED' },
}, () => {
const err = new Error('boom');
err.code = 'ERR_ACTUAL';
throw err;
});要为 expectFailure 提供原因和特定错误,请使用 { label, match }。
it('should fail with specific error and reason', {
expectFailure: {
label: 'reason for failure',
match: /error message/,
},
}, () => {
assert.strictEqual(doTheThing(), true);
});skip 和/或 todo 与 expectFailure 互斥,skip 或 todo
在同时应用时将“获胜”(skip 胜过两者,todo 胜过
expectFailure)。
这些测试将被跳过(且不运行):
it.expectFailure('should do the thing', { skip: true }, () => {
assert.strictEqual(doTheThing(), true);
});
it.skip('should do the thing', { expectFailure: true }, () => {
assert.strictEqual(doTheThing(), true);
});这些测试将被标记为 "todo"(静默错误):
it.expectFailure('should do the thing', { todo: true }, () => {
assert.strictEqual(doTheThing(), true);
});
it.todo('should do the thing', { expectFailure: true }, () => {
assert.strictEqual(doTheThing(), true);
});如果 Node.js 使用 --test-only 命令行选项启动,或者测试隔离被禁用,则可以通过向应该运行的测试传递 only 选项来跳过除选定子集之外的所有测试。当设置了带有 only 选项的测试时,所有子测试也会运行。
如果套件设置了 only 选项,则运行套件内的所有测试,除非它有设置了 only 选项的后代,在这种情况下,只运行那些测试。
当在 test()/it() 中使用 子测试 时,需要标记所有祖先测试带有 only 选项,以便仅运行选定的测试子集。
测试上下文的 runOnly() 方法可用于在子测试级别实现相同的行为。未执行的测试将从测试运行器输出中省略。
// 假设 Node.js 是使用 --test-only 命令行选项运行的。
// 套件的 'only' 选项已设置,因此运行这些测试。
test('this test is run', { only: true }, async (t) => {
// 在此测试内,默认运行所有子测试。
await t.test('running subtest');
// 可以更新测试上下文以运行带有 'only' 选项的子测试。
t.runOnly(true);
await t.test('this subtest is now skipped');
await t.test('this subtest is run', { only: true });
// 将上下文切换回以执行所有测试。
t.runOnly(false);
await t.test('this subtest is now run');
// 显式不运行这些测试。
await t.test('skipped subtest 3', { only: false });
await t.test('skipped subtest 4', { skip: true });
});
// 未设置 'only' 选项,因此跳过此测试。
test('this test is not run', () => {
// 此代码未运行。
throw new Error('fail');
});
describe('a suite', () => {
// 已设置 'only' 选项,因此运行此测试。
it('this test is run', { only: true }, () => {
// 此代码已运行。
});
it('this test is not run', () => {
// 此代码未运行。
throw new Error('fail');
});
});
describe.only('a suite', () => {
// 已设置 'only' 选项,因此运行此测试。
it('this test is run', () => {
// 此代码已运行。
});
it('this test 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');
});
// 测试在此行之后结束。
});监视模式
History
稳定性:1 - 实验性
Node.js 测试运行器支持通过传递 --watch 标志以监视模式运行:
node --test --watch在监视模式下,测试运行器将监视测试文件及其依赖项的更改。检测到更改时,测试运行器将重新运行受更改影响的测试。 测试运行器将持续运行,直到进程终止。
全局设置和清理
History
稳定性:1.0 - 早期开发
测试运行器支持指定一个模块,该模块将在所有测试执行之前进行评估,并可用于设置测试的全局状态或夹具。这对于准备多个测试所需的资源或设置共享状态很有用。
该模块可以导出以下任何内容:
- 一个
globalSetup函数,在所有测试开始前运行一次 - 一个
globalTeardown函数,在所有测试完成后运行一次
该模块在使用命令行运行测试时使用 --test-global-setup 标志指定。
// setup-module.js
async function globalSetup() {
// 设置共享资源、状态或环境
console.log('Global setup executed');
// 运行服务器、创建文件、准备数据库等。
}
async function globalTeardown() {
// 清理资源、状态或环境
console.log('Global teardown executed');
// 关闭服务器、移除文件、断开数据库连接等。
}
module.exports = { globalSetup, globalTeardown };如果全局设置函数抛出错误,将不会运行任何测试,进程将以非零退出码退出。 在这种情况下,全局清理函数不会被调用。
可以通过传递 --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-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"随机化测试执行顺序
History
稳定性:1.0 - 早期开发
测试运行器可以随机化执行顺序以帮助检测依赖顺序的测试。启用时,运行器会随机化发现的文件以及每个文件内排队的测试。使用 --test-randomize 启用此模式。
node --test --test-randomize启用随机化时,测试运行器会将用于运行的种子作为诊断消息打印出来:
Randomized test order seed: 12345使用 --test-random-seed=<number> 以确定性地重放相同的随机顺序。提供 --test-random-seed 也会启用随机化,因此提供种子时 --test-randomize 是可选的:
node --test --test-random-seed=12345在大多数测试文件中,随机化会自动工作。一个重要的例外是当子测试被逐个等待时。在这种模式下,每个子测试仅在前一个完成后才开始,因此运行器保持声明顺序而不是随机化它。
示例:这是顺序运行的,未随机化。
import test from 'node:test';
test('math', async (t) => {
for (const name of ['adds', 'subtracts', 'multiplies']) {
// 顺序等待每个子测试会保留声明顺序。
await t.test(name, async () => {});
}
});使用套件风格的 API(如 describe()/it() 或 suite()/test())仍然允许随机化,因为兄弟测试是一起排队的。
示例:这仍然符合随机化条件。
import { describe, it } from 'node:test';
describe('math', () => {
it('adds', () => {});
it('subtracts', () => {});
it('multiplies', () => {});
});--test-randomize 和 --test-random-seed 不支持与 --watch 模式一起使用。
匹配的文件作为测试文件执行。 有关测试文件执行的更多信息,可以在 测试运行器执行模型 部分找到。
当启用进程级测试隔离时,每个匹配的测试文件都在单独的子进程中执行。任何时间运行的子进程的最大数量由 --test-concurrency 标志控制。如果子进程以退出码 0 结束,则测试视为通过。否则,测试视为失败。测试文件必须可由 Node.js 执行,但不需要在内部使用 node:test 模块。
每个测试文件的执行都好像它是一个常规脚本。也就是说,如果测试文件本身使用 node:test 来定义测试,则所有这些测试都将在单个应用程序线程内执行,无论 test() 的 concurrency 选项值如何。
当禁用进程级测试隔离时,每个匹配的测试文件都导入到测试运行器进程中。加载所有测试文件后,顶层测试以并发数 1 执行。因为所有测试文件都在同一上下文中运行,所以测试可能以启用隔离时不可能的方式相互交互。例如,如果测试依赖于全局状态,则该状态可能被源自另一个文件的测试修改。
在进程隔离模式下运行测试时(默认情况),生成的子进程会从父进程继承 Node.js 选项,包括 配置文件 中指定的选项。但是,某些标志会被过滤掉以启用正确的测试运行器功能:
--test- 防止递归执行测试--experimental-test-coverage- 由测试运行器管理--watch- 监视模式在父级处理--experimental-default-config-file- 配置文件加载由父级处理--test-reporter- 报告由父进程管理--test-reporter-destination- 输出目标由父级控制--experimental-config-file- 配置文件路径由父级管理--test-randomize- 随机化由父进程管理并传播到子进程--test-random-seed- 随机化种子由父进程管理并传播到子进程
来自命令行参数、环境变量和配置文件的所有其他 Node.js 选项都由子进程继承。
稳定性:1 - 实验性
当 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) {
// 此分支中的代码永远不会被执行,但这些行会被忽略以用于
// 覆盖率目的。'disable' 注释之后的所有行都会被忽略
// 直到遇到相应的 'enable' 注释。
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:test 模块支持通过顶层 mock 对象在测试期间进行模拟。以下示例创建一个函数的间谍,该函数将两个数字相加。然后使用间谍来断言该函数是否按预期被调用。
import assert from 'node:assert';
import { mock, test } from 'node:test';
test('spies on a function', () => {
const sum = mock.fn((a, b) => {
return a + b;
});
assert.strictEqual(sum.mock.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);
// 重置全局跟踪的模拟对象。
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 模块中的 setTimeout 函数,
以及来自 Node.js 全局上下文的函数。
注意: 此 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();
// 可选地选择要模拟的内容
mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// 推进时间
mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
// 重置全局跟踪的模拟对象。
mock.timers.reset();
// 如果调用 reset 模拟实例,它也会重置计时器实例
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();
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(fn, 9999);
assert.strictEqual(fn.mock.callCount(), 0);
// 推进时间
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) => {
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['Date'] });
// 如果未指定,初始日期将基于 UNIX 纪元的 0
assert.strictEqual(Date.now(), 0);
// 推进时间也会推进日期
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) => {
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// 推进时间也会推进日期
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) => {
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['Date'], now: 100 });
assert.strictEqual(Date.now(), 100);
// 推进时间也会推进日期
context.mock.timers.setTime(1000);
context.mock.timers.tick(200);
assert.strictEqual(Date.now(), 1200);
});当你调用 setTime() 时,过去安排的计时器不会运行。要执行这些计时器,你可以使用
.tick() 方法从新时间向前移动。
import assert from 'node:assert';
import { test } from 'node:test';
test('setTime does not execute timers', (context) => {
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const fn = context.mock.fn();
setTimeout(fn, 1000);
context.mock.timers.setTime(800);
// 计时器未执行,因为时间尚未到达
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(Date.now(), 800);
context.mock.timers.setTime(1200);
// 计时器仍然未执行
assert.strictEqual(fn.mock.callCount(), 0);
// 推进时间以执行计时器
context.mock.timers.tick(0);
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) => {
// 可选地选择要模拟的内容
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();
// 所有计时器已执行,因为时间现已到达
assert.strictEqual(fn.mock.callCount(), 3);
assert.strictEqual(Date.now(), 3000);
});快照测试
History
快照测试不再是实验性功能。
快照测试允许将任意值序列化为字符串值,并与一组已知良好值进行比较。这些已知良好值被称为快照,并存储在快照文件中。快照文件由测试运行器管理,但设计为人类可读以辅助调试。最佳实践是将快照文件与测试文件一起检入版本控制。
快照文件是通过使用 --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 标志。现在测试应该通过。
测试报告器
History
非 TTY 标准输出上的默认报告器已从 tap 更改为 spec,与 TTY 标准输出保持一致。
报告器现在暴露在 node:test/reporters 上。
node:test 模块支持传递 --test-reporter 标志,以便测试运行器使用特定的报告器。
支持以下内置报告器:
-
specspec报告器以人类可读格式输出测试结果。这是默认报告器。 -
taptap报告器以 TAP 格式输出测试结果。 -
dotdot报告器以紧凑格式输出测试结果, 其中每个通过的测试由一个.表示, 每个失败的测试由一个X表示。 -
junitjunit 报告器以 jUnit XML 格式输出测试结果 -
lcovlcov报告器在与--experimental-test-coverage标志一起使用时输出测试覆盖率。
这些报告器的确切输出可能会在 Node.js 版本之间发生变化,不应以编程方式依赖。如果需要以编程方式访问测试运行器的输出,请使用由 {TestsStream} 发出的事件。
报告器可通过 node:test/reporters 模块获得:
import { tap, spec, dot, junit, lcov } from '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;使用生成器函数的自定义报告器示例:
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;
}
}
}
}提供给 --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
run(options?): void<Object>true
,它将并行运行
os.availableParallelism() - 1
个测试文件。
如果为
false
,它将一次只运行一个测试文件。
默认值:
false
。<boolean>false
。<number>
|
<Function>process.debugPort
递增。如果
isolation
选项设置为
'none'
,则忽略此选项,因为不会生成子进程。
默认值:
undefined
。<string>'process'
,每个测试文件在单独的子进程中运行。如果设置为
'none'
,所有测试文件在当前进程中运行。
默认值:
'process'
。<boolean>only
选项设置的测试<Function>TestsStream
实例的函数,
可用于在任何测试运行之前设置监听器。
默认值:
undefined
。<Array>node
可执行文件的 CLI 标志数组。当
isolation
为
'none'
时,此选项无效。
默认值:
[]<Array>isolation
为
'none'
时,此选项无效。
默认值:
[]
。<AbortSignal>beforeEach()
,也会运行。
默认值:
undefined
。beforeEach()
,也会运行。
默认值:
undefined
。<number>Infinity
。<boolean>false
。<Object>undefined
。<boolean>watch: true
。
默认值:
false
。<number>0
到
4294967295
之间的整数。
默认值:
undefined
。<string>undefined
。coverage
设置为
true
时适用。
如果同时提供了
coverageExcludeGlobs
和
coverageIncludeGlobs
,
文件必须满足
两者
标准才能包含在覆盖率报告中。
默认值:
undefined
。coverage
设置为
true
时适用。
如果同时提供了
coverageExcludeGlobs
和
coverageIncludeGlobs
,
文件必须满足
两者
标准才能包含在覆盖率报告中。
默认值:
undefined
。<number>1
退出。
默认值:
0
。<number>1
退出。
默认值:
0
。<number>1
退出。
默认值:
0
。<Object>isolation='none'
不兼容。这些变量将覆盖
主进程中的变量,并且不与
process.env
合并。
默认值:
process.env
。注意: 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);suite(name?, options?, fn?): void<string>fn
的
name
属性,如果
fn
没有名称则为
'<anonymous>'
。<Object>test([name][, options][, fn])
相同的选项。SuiteContext
对象。
默认:
空操作函数。<Promise>
立即用
undefined
兑现。suite() 函数从 node:test 模块导入。
suite.skip(name?, options?, fn?): void跳过套件的简写。这与 [suite([name], { skip: true }[, fn])][suite options] 相同。
suite.todo(name?, options?, fn?): void将套件标记为 TODO 的简写。这与 [suite([name], { todo: true }[, fn])][suite options] 相同。
suite.only(name?, options?, fn?): void将套件标记为 only 的简写。这与 [suite([name], { only: true }[, fn])][suite options] 相同。
test
History
添加了 skip、todo 和 only 简写。
添加 signal 选项。
添加 timeout 选项。
test(name?, options?, fn?): void<string>fn
的
name
属性,如果
fn
没有名称则为
'<anonymous>'
。<Object>true
,所有计划的异步测试将在线程内并发运行。如果为
false
,一次只运行一个测试。
如果未指定,子测试将从其父级继承此值。
默认:
false
。<RegExp>
|
<Function>
|
<Object>
|
<Error>
( without wrapping in
{ match: … }
),则仅当抛出的错误匹配时测试才通过,遵循
assert.throws
的行为。要同时提供原因和验证,请传递一个包含
label
(字符串)和
match
(RegExp、Function、Object 或 Error)的对象。
默认:
false
。<boolean>only
测试,则此测试将被运行。否则,测试将被跳过。
默认:
false
。<AbortSignal><number>Infinity
。<number>undefined
。TestContext
对象。如果测试使用回调,
回调函数作为第二个参数传递。
默认:
空操作函数。<Promise>
一旦测试完成即兑现为
undefined
,如果测试在套件内运行则立即兑现。test() 函数是从 test 模块导入的值。每次调用此函数都会导致向 {TestsStream} 报告测试。
传递给 fn 参数的 TestContext 对象可用于执行与当前测试相关的操作。示例包括跳过测试、添加
额外的诊断信息或创建子测试。
test() 返回一个 Promise,一旦测试完成即兑现。
如果 test() 在套件内调用,它会立即兑现。
顶层测试的返回值通常可以丢弃。
但是,子测试的返回值应该被使用,以防止父测试
先完成并取消子测试,如下例所示。
test('top level test', async (t) => {
// 如果移除下一行的 'await',以下子测试中的 setTimeout() 会导致其生命周期超过
// 父测试。一旦父测试完成,它将取消任何未完成的子测试。
await t.test('longer running subtest', async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});timeout 选项可用于如果测试完成时间超过 timeout 毫秒则使测试失败。但是,它不是
取消测试的可靠机制,因为运行中的测试可能会阻塞应用程序线程,
从而阻止计划的取消。
test.skip(name?, options?, fn?): void跳过测试的简写,
与 [test([name], { skip: true }[, fn])][it options] 相同。
test.todo(name?, options?, fn?): void将测试标记为 TODO 的简写,
与 [test([name], { todo: true }[, fn])][it options] 相同。
test.only(name?, options?, fn?): void将测试标记为 only 的简写,
与 [test([name], { only: true }[, fn])][it options] 相同。
describe(name?, options?, fn?): voidsuite() 的别名。
describe() 函数从 node:test 模块导入。
describe.skip(name?, options?, fn?): void跳过套件的简写。这与
[describe([name], { skip: true }[, fn])][describe options] 相同。
describe.todo(name?, options?, fn?): void将套件标记为 TODO 的简写。这与
[describe([name], { todo: true }[, fn])][describe options] 相同。
describe.only(name?, options?, fn?): void将套件标记为 only 的简写。这与
[describe([name], { only: true }[, fn])][describe options] 相同。
it(name?, options?, fn?): voidtest() 的别名。
it() 函数从 node:test 模块导入。
it.skip(name?, options?, fn?): void跳过测试的简写,
与 [it([name], { skip: true }[, fn])][it options] 相同。
it.todo(name?, options?, fn?): void将测试标记为 TODO 的简写,
与 [it([name], { todo: true }[, fn])][it options] 相同。
it.only(name?, options?, fn?): void将测试标记为 only 的简写,
与 [it([name], { only: true }[, fn])][it options] 相同。
before(fn?, options?): void<Object><AbortSignal><number>Infinity
。此函数创建一个在执行套件之前运行的钩子。
describe('tests', async () => {
before(() => console.log('about to run some test'));
it('is a subtest', () => {
// 此处是一些相关的断言
});
});after(fn?, options?): void<Object><AbortSignal><number>Infinity
。此函数创建一个在执行套件之后运行的钩子。
describe('tests', async () => {
after(() => console.log('finished running tests'));
it('is a subtest', () => {
// 此处是一些相关的断言
});
});注意: after 钩子保证会运行,
即使套件内的测试失败。
beforeEach(fn?, options?): void<Object><AbortSignal><number>Infinity
。此函数创建一个在当前套件中每个测试之前运行的钩子。
describe('tests', async () => {
beforeEach(() => console.log('about to run a test'));
it('is a subtest', () => {
// 此处是一些相关的断言
});
});afterEach(fn?, options?): void<Object><AbortSignal><number>Infinity
。此函数创建一个钩子,在当前套件中的每个测试后运行。
即使测试失败,afterEach() 钩子也会运行。
describe('tests', async () => {
afterEach(() => console.log('finished running a test'));
it('is a subtest', () => {
// 此处是一些相关的断言
});
});assert
History
一个对象,其方法用于配置当前进程中 TestContext 对象上可用的断言。默认情况下,node:assert 的方法和快照测试函数可用。
可以通过将通用配置代码放在使用 --require 或 --import 预加载的模块中,从而将相同的配置应用于所有文件。
assert.register(name, fn): void使用提供的名称和函数定义一个新的断言函数。如果已存在同名的断言,则会被覆盖。
snapshot
History
一个对象,其方法用于配置当前进程中的默认快照设置。可以通过将通用配置代码放在使用 --require 或 --import 预加载的模块中,从而将相同的配置应用于所有文件。
snapshot.setDefaultSnapshotSerializers(serializers): void<Array>此函数用于自定义测试运行器使用的默认序列化机制。默认情况下,测试运行器通过对提供的值调用 JSON.stringify(value, null, 2) 来执行序列化。JSON.stringify() 在循环结构和支持的数据类型方面确实存在局限性。如果需要更健壮的序列化机制,应使用此函数。
snapshot.setResolveSnapshotPath(fn): void<Function>fn()
必须返回一个字符串,指定快照文件的位置。此函数用于自定义用于快照测试的快照文件的位置。默认情况下,快照文件名与入口点文件名相同,带有 .snapshot 文件扩展名。
MockFunctionContext 类用于检查或操纵通过 MockTracker API 创建的 mock 的行为。
- 类型:
<Array>
一个 getter,返回用于跟踪对 mock 调用的内部数组的副本。数组中的每个条目都是一个具有以下属性的对象。
<Array><any>undefined
。<any><Error>Error
对象,其堆栈可用于确定被 mock 函数调用的调用站点。<Function>
|
<undefined>undefined
。<any>this
值。ctx.callCount(): void- 返回:
<integer>此 mock 被调用的次数。
此函数返回此 mock 被调用的次数。此函数比检查 ctx.calls.length 更高效,因为 ctx.calls 是一个创建内部调用跟踪数组副本的 getter。
ctx.mockImplementation(implementation): void<Function>
|
<AsyncFunction>此函数用于更改现有 mock 的行为。
以下示例使用 t.mock.fn() 创建一个 mock 函数,调用该 mock 函数,然后将 mock 实现更改为不同的函数。
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?): void<Function>
|
<AsyncFunction>onCall
指定的调用编号的 mock 实现的函数。<integer>implementation
的调用编号。如果
指定的调用已经发生,则抛出异常。
默认值:
下一次调用的编号。此函数用于更改现有 mock 单次调用的行为。一旦调用 onCall 发生,mock 将恢复为如果没有调用 mockImplementationOnce() 本应使用的行为。
以下示例使用 t.mock.fn() 创建一个 mock 函数,调用该 mock 函数,将下一次调用的 mock 实现更改为不同的函数,然后恢复其之前的行为。
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(): void重置 mock 函数的调用历史。
ctx.restore(): void将 mock 函数的实现重置为其原始行为。调用此函数后仍可继续使用 mock。
稳定性:1.0 - 早期开发
MockModuleContext 类用于操纵通过 MockTracker API 创建的模块 mock 的行为。
ctx.restore(): void重置 mock 模块的实现。
MockPropertyContext 类用于检查或操纵通过 MockTracker API 创建的属性 mock 的行为。
- 类型:
<Array>
一个 getter,返回用于跟踪对被 mock 属性访问(get/set)的内部数组的副本。数组中的每个条目都是一个具有以下属性的对象:
ctx.accessCount(): void- 返回:
<integer>属性被访问(读取或写入)的次数。
此函数返回属性被访问的次数。
此函数比检查 ctx.accesses.length 更高效,因为
ctx.accesses 是一个创建内部访问跟踪数组副本的 getter。
ctx.mockImplementation(value): void<any>此函数用于更改被 mock 属性 getter 返回的值。
ctx.mockImplementationOnce(value, onAccess?): void此函数用于更改现有 mock 单次调用的行为。一旦调用 onAccess 发生,mock 将恢复为如果没有调用 mockImplementationOnce() 本应使用的行为。
以下示例使用 t.mock.property() 创建一个 mock 函数,调用该 mock 属性,将下一次调用的 mock 实现更改为不同的值,然后恢复其之前的行为。
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);
});为了与其余 mocking API 保持一致,此函数将属性获取和设置都视为访问。如果在同一访问索引处发生属性设置,则"once"值将被设置操作消耗,并且被 mock 的属性值将更改为"once"值。如果您打算仅将"once"值用于 get 操作,这可能会导致意外行为。
ctx.resetAccesses(): void重置被 mock 属性的访问历史。
ctx.restore(): void将 mock 属性的实现重置为其原始行为。调用此函数后仍可继续使用 mock。
类:MockTracker
History
MockTracker 类用于管理模拟功能。测试运行器模块提供一个顶层 mock 导出,它是一个 MockTracker 实例。
每个测试也通过测试上下文的 mock 属性提供其自己的 MockTracker 实例。
mock.fn(original?, implementation?, options?): void<Function>
|
<AsyncFunction><Function>
|
<AsyncFunction>original
的模拟实现。这对于创建模拟很有用,
这些模拟在指定数量的调用中表现一种行为,然后恢复
original
的行为。
默认值:
original
指定的函数。<Object><integer>implementation
行为的次数。一旦模拟函数被调用
times
次,它
将自动恢复
original
的行为。此值必须是大于零的整数。
默认值:
Infinity
。此函数用于创建模拟函数。
以下示例创建了一个模拟函数,该函数在每次调用时将计数器加一。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?): void此函数是 MockTracker.method 的语法糖,其中 options.getter
设置为 true。
mock.method(object, methodName, implementation?, options?): void<Object><Function>
|
<AsyncFunction>object[methodName]
的模拟实现。
默认值:
object[methodName]
指定的原始方法。<Object>此函数用于在现有对象方法上创建模拟。以下 示例演示了如何在现有对象方法上创建模拟。
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?): void稳定性:1.0 - 早期开发
<Object><boolean>false
,每次调用
require()
或
import()
都会生成一个新的模拟模块。如果为
true
,后续调用将返回相同的
模块模拟,并且模拟模块会插入到 CommonJS 缓存中。
默认值:
false。<Object>default
属性,则
用作模拟模块的默认导出。所有其他自有可枚举属性用作命名导出。
此选项不能与 defaultExport 或 namedExports 一起使用。<any>module.exports
的值。如果未提供此值,CJS 和内置
模拟使用空对象作为
module.exports
的值。
此选项不能与 options.exports 一起使用。
此选项已弃用,将在以后的版本中移除。
推荐使用
options.exports.default
。<Object>module.exports
上。因此,如果
模拟是使用命名导出和非对象默认导出创建的,则
当模拟用作 CJS 或内置模块时,模拟将抛出异常。
此选项不能与 options.exports 一起使用。
此选项已弃用,将在以后的版本中移除。
推荐使用
options.exports
。此函数用于模拟 ECMAScript 模块、CommonJS 模块、JSON 模块和
Node.js 内置模块的导出。模拟之前对原始模块的任何引用都不会受到影响。为了
启用模块模拟,必须使用 --experimental-test-module-mocks 命令行标志启动 Node.js。
注意:通过 同步 API 注册的 模块自定义钩子 会影响提供给 mock.module 的 specifier 的解析。
通过 异步 API 注册的自定义钩子当前会被忽略(因为测试运行器的加载器是同步的,且 Node 不支持多链/跨链加载)。
以下示例演示了如何为模块创建模拟。
test('mocks a builtin module in both module systems', async (t) => {
// 创建一个 'node:readline' 的模拟,带有一个名为 'foo' 的命名导出,该导出
// 在原始的 'node:readline' 模块中不存在。
const mock = t.mock.module('node:readline', {
exports: { foo: () => 42 },
});
let esmImpl = await import('node:readline');
let cjsImpl = require('node:readline');
// cursorTo() 是原始 'node:readline' 模块的一个导出。
assert.strictEqual(esmImpl.cursorTo, undefined);
assert.strictEqual(cjsImpl.cursorTo, undefined);
assert.strictEqual(esmImpl.fn(), 42);
assert.strictEqual(cjsImpl.fn(), 42);
mock.restore();
// 模拟已恢复,因此返回原始内置模块。
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?): void为对象上的属性值创建模拟。这允许你跟踪和控制对特定属性的访问, 包括它被读取(getter)或写入(setter)的次数,并在模拟后恢复原始值。
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(): void此函数恢复此前由该 MockTracker 创建的所有模拟的默认行为,并将模拟与
MockTracker 实例解除关联。一旦解除关联,模拟仍可使用,但
MockTracker 实例不再可用于重置其行为或
以其他方式与它们交互。
每个测试完成后,会在测试上下文的
MockTracker 上调用此函数。如果广泛使用全局 MockTracker,建议手动调用此
函数。
mock.restoreAll(): void此函数恢复此前由该 MockTracker 创建的所有模拟的默认行为。与 mock.reset() 不同,mock.restoreAll() 不
会将模拟与 MockTracker 实例解除关联。
mock.setter(object, methodName, implementation?, options?): void此函数是 MockTracker.method 的语法糖,其中 options.setter
设置为 true。
类:MockTimers
History
Mock 计时器现已稳定。
模拟计时器是一种常用于软件测试的技术,用于模拟和控制计时器(例如 setInterval 和 setTimeout)的行为,而无需实际等待指定的时间间隔。
MockTimers 也能够模拟 Date 对象。
MockTracker 提供一个顶层的 timers 导出,它是一个 MockTimers 实例。
timers.enable
History
更新参数为选项对象,包含可用 API和默认初始纪元。
timers.enable(enableOptions?): void为指定的计时器启用计时器模拟。
<Object><Array>'setInterval'
、
'setTimeout'
、
'setImmediate'
和
'Date'
。
默认值:
['setInterval', 'setTimeout', 'setImmediate', 'Date']
。
如果未提供数组,默认情况下所有时间相关的 API(
'setInterval'
、
'clearInterval'
、
'setTimeout'
、
'clearTimeout'
、
'setImmediate'
、
'clearImmediate'
和
'Date'
)都将被模拟。注意: 当你为特定计时器启用模拟时,其关联的清除函数也将被隐式模拟。
注意: 模拟 Date 会影响模拟计时器的行为,因为它们使用相同的内部时钟。
未设置初始时间的示例用法:
import { mock } from '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 });设置初始 Date 对象作为时间的示例用法
import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: new Date() });或者,如果你在不带任何参数的情况下调用 mock.timers.enable():
所有计时器('setInterval'、'clearInterval'、'setTimeout'、'clearTimeout'、
'setImmediate' 和 'clearImmediate')将被模拟。来自 node:timers、node:timers/promises 和
globalThis 的 setInterval、
clearInterval、setTimeout、clearTimeout、setImmediate 和
clearImmediate 函数将被模拟。以及全局 Date 对象。
timers.reset(): void此函数恢复此前由该 MockTimers 实例创建的所有模拟的默认行为,并将模拟从 MockTracker 实例解除关联。
注意: 在每个测试完成后,此函数会在测试上下文的 MockTracker 上被调用。
import { mock } from 'node:test';
mock.timers.reset();timers[Symbol.dispose](): void调用 timers.reset()。
timers.tick(milliseconds?): void推进所有模拟计时器的时间。
<number>1
。注意: 这与 Node.js 中 setTimeout 的行为不同,它只接受正数。在 Node.js 中,带负数的 setTimeout 仅出于 Web 兼容性原因受支持。
以下示例模拟了一个 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);
// 推进时间
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);
});使用 .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);
// 推进时间
context.mock.timers.tick(9999);
assert.strictEqual(fn.mock.callCount(), 1);
assert.strictEqual(Date.now(), 9999);
});如前所述,来自计时器的所有清除函数(clearTimeout、clearInterval 和
clearImmediate)都被隐式模拟。看看这个使用 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();
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['setTimeout'] });
const id = setTimeout(fn, 9999);
// 也被隐式模拟
clearTimeout(id);
context.mock.timers.tick(9999);
// 由于该 setTimeout 被清除,模拟函数将永远不会被调用
assert.strictEqual(fn.mock.callCount(), 0);
});一旦你启用计时器模拟,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();
// 可选地选择要模拟的内容
context.mock.timers.enable({ apis: ['setTimeout'] });
setTimeout(globalTimeoutObjectSpy, 9999);
nodeTimers.setTimeout(nodeTimerSpy, 9999);
const promise = nodeTimersPromises.setTimeout(9999).then(nodeTimerPromiseSpy);
// 推进时间
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));
}
});timers.runAll(): void立即触发所有待处理的模拟计时器。如果 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);
// 注意,如果两个计时器具有相同的超时时间,
// 执行顺序是有保证的
setTimeout(() => results.push(3), 8888);
setTimeout(() => results.push(2), 8888);
assert.deepStrictEqual(results, []);
context.mock.timers.runAll();
assert.deepStrictEqual(results, [3, 2, 1]);
// Date 对象也被推进到最远计时器的时间
assert.strictEqual(Date.now(), 9999);
});注意: runAll() 函数是专门为
在计时器模拟上下文中触发计时器而设计的。
它对实时系统系统时钟或模拟环境之外的实际计时器没有任何影响。
timers.setTime(milliseconds): void设置当前的 Unix 时间戳,它将用作任何模拟 Date 对象的参考。
import assert from 'node:assert';
import { test } from 'node:test';
test('runAll functions following the given order', (context) => {
const now = Date.now();
const setTime = 1000;
// Date.now 未被模拟
assert.deepStrictEqual(Date.now(), now);
context.mock.timers.enable({ apis: ['Date'] });
context.mock.timers.setTime(setTime);
// Date.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, []);
// 日期已推进但计时器未走动
assert.strictEqual(Date.now(), 12000);
});类:TestsStream
History
当测试为套件时,为 test:pass 和 test:fail 事件添加了类型。
- 继承自 {Readable}
成功调用 run() 方法将返回一个新的 {TestsStream} 对象,流式传输一系列代表测试执行的事件。 TestsStream 将按测试定义的顺序发出事件。
某些事件保证按测试定义的顺序发出,而其他事件则按测试执行的顺序发出。
<Object><Object><Array><string><number><number><number><number><number><number><number><number><number><Object><Object><number><number><number><number><number><number><number><number><number><string><number>当启用代码覆盖率且所有测试完成后发出。
<Object><number>
|
<undefined>undefined
。<Object><string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number><number><string>
|
<boolean>
|
<undefined>context.todo
则存在<string>
|
<boolean>
|
<undefined>context.skip
则存在当测试完成执行时发出。
此事件的发出顺序与测试定义的顺序不一致。
对应的声明顺序事件是 'test:pass' 和 'test:fail'。
<Object><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number><string>'suite'
或
'test'
。当测试出队时发出,就在执行之前。
此事件不保证按测试定义的顺序发出。对应的声明顺序事件是 'test:start'。
<Object><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number>当调用 context.diagnostic 时发出。
此事件保证按测试定义的顺序发出。
<Object><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number><string>'suite'
或
'test'
。当测试入队等待执行时发出。
<Object><number>
|
<undefined>undefined
。<Object><number><string>
|
<undefined><number>
|
<undefined>--test-rerun-failures
标志时存在。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number><number><string>
|
<boolean>
|
<undefined>context.todo
则存在<string>
|
<boolean>
|
<undefined>context.skip
则存在当测试失败时发出。
此事件保证按测试定义的顺序发出。
对应的执行顺序事件是 'test:complete'。
事件:'test:interrupted'
History
<Object><Array><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number>当测试运行器被 SIGINT 信号中断时发出(例如,按下 Ctrl+C 时)。该事件包含有关中断时正在运行的测试的信息。
当使用进程隔离(默认)时,测试名称将是文件路径,因为父运行器只知道文件级别的测试。当使用 --test-isolation=none 时,将显示实际的测试名称。
<Object><number>
|
<undefined>undefined
。<Object><number><string>
|
<undefined><number>
|
<undefined>--test-rerun-failures
标志时存在。<number>
|
<undefined>--test-rerun-failures
标志时存在。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number><number><string>
|
<boolean>
|
<undefined>context.todo
则存在<string>
|
<boolean>
|
<undefined>context.skip
则存在当测试通过时发出。
此事件保证按测试定义的顺序发出。
对应的执行顺序事件是 'test:complete'。
<Object><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<number><number>当给定测试的所有子测试完成后发出。 此事件保证按测试定义的顺序发出。
<Object><number>
|
<undefined>undefined
。<string>
|
<undefined>undefined
。<number>
|
<undefined>undefined
。<string><number>当测试开始报告自身及其子测试的状态时发出。
此事件保证按测试定义的顺序发出。
对应的执行顺序事件是 'test:dequeue'。
当运行中的测试写入 stderr 时发出。
仅当传递了 --test 标志时才会发出此事件。
此事件不保证按测试定义的顺序发出。
当运行中的测试写入 stdout 时发出。
仅当传递了 --test 标志时才会发出此事件。
此事件不保证按测试定义的顺序发出。
当测试运行完成时发出。此事件包含与完成的测试运行相关的指标,有助于确定测试运行是通过还是失败。如果使用进程级测试隔离,除了最终的累计摘要外,还会为每个测试文件生成一个 'test:summary' 事件。
当监视模式下没有更多测试排队等待执行时发出。
当监视模式下由于文件更改而重新启动一个或多个测试时发出。
getTestContext(): void- 返回值:
<undefined>
返回与当前正在执行的测试或套件关联的 TestContext 或 SuiteContext 对象,如果在测试或套件之外调用,则返回 undefined。此函数可用于从测试或套件函数内部或其中的任何异步操作中访问上下文信息。
import { getTestContext } from 'node:test';
test('example test', async () => {
const ctx = getTestContext();
console.log(`Running test: ${ctx.name}`);
});
describe('example suite', () => {
const ctx = getTestContext();
console.log(`Running suite: ${ctx.name}`);
});当从测试中调用时,返回 TestContext。
当从套件中调用时,返回 SuiteContext。
如果在测试或套件之外调用(例如,在模块的顶层或在执行完成后的 setTimeout 回调中),此函数返回 undefined。
当从钩子(before、beforeEach、after、afterEach)内部调用时,此函数返回与该钩子关联的测试或套件的上下文。
类:TestContext
History
The before function was added to TestContext.
TestContext 的实例会传递给每个测试函数,以便与测试运行器交互。但是,TestContext 构造函数并未作为 API 的一部分暴露。
context.before(fn?, options?): voidTestContext
对象。如果钩子使用回调,则回调函数作为第二个参数传递。
默认值:
一个空操作函数。<Object><AbortSignal><number>Infinity
。此函数用于创建一个钩子,在当前测试的子测试之前运行。
context.beforeEach(fn?, options?): voidTestContext
对象。如果钩子使用回调,则回调函数作为第二个参数传递。
默认值:
一个空操作函数。<Object><AbortSignal><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) => {
// 此处进行相关的断言
},
);
});context.after(fn?, options?): voidTestContext
对象。如果钩子使用回调,则回调函数作为第二个参数传递。
默认值:
一个空操作函数。<Object><AbortSignal><number>Infinity
。此函数用于创建一个钩子,在当前测试完成后运行。
test('top level test', async (t) => {
t.after((t) => t.diagnostic(`finished running ${t.name}`));
// 此处进行相关的断言
});context.afterEach(fn?, options?): voidTestContext
对象。如果钩子使用回调,则回调函数作为第二个参数传递。
默认值:
一个空操作函数。<Object><AbortSignal><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) => {
// 此处进行相关的断言
},
);
});一个包含绑定到 context 的断言方法的对象。node:assert 模块中的顶层函数在此处暴露,用于创建测试计划。
test('test', (t) => {
t.plan(1);
t.assert.strictEqual(true, true);
});context.assert.fileSnapshot(value, path, options?): void<any>--test-update-snapshots
标志启动的,则序列化的值将写入
path
。否则,序列化的值将与现有快照文件的内容进行比较。<string>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?): void<any>--test-update-snapshots
标志启动的,则序列化的值将写入快照文件。否则,序列化的值将与现有快照文件中的相应值进行比较。此函数实现快照测试的断言。
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): void<string>此函数用于将诊断信息写入输出。任何诊断信息都包含在测试结果的末尾。此函数不返回值。
test('top level test', (t) => {
t.diagnostic('A diagnostic message');
});创建当前测试的测试文件的绝对路径。如果测试文件导入生成测试的其他模块,则导入的测试将返回根测试文件的路径。
测试及其每个祖先的名称,由 > 分隔。
测试的名称。
- 类型:
<boolean>在测试执行之前为false,例如在beforeEach钩子中。
指示测试是否成功。
测试/用例的失败原因;已包装并可通过 context.error.cause 访问。
- 类型:
<number>
测试的尝试次数。此值基于零,因此第一次尝试是 0,第二次尝试是 1,依此类推。此属性与 --test-rerun-failures 选项结合使用很有用,可确定测试当前正在运行哪次尝试。
- 类型:
<number>|<undefined>
运行当前测试文件的工作线程的唯一标识符。此值源自 NODE_TEST_WORKER_ID 环境变量。当使用 --test-isolation=process(默认值)运行测试时,每个测试文件在单独的子进程中运行,并被分配一个从 1 到 N 的工作线程 ID,其中 N 是并发工作线程的数量。当使用 --test-isolation=none 运行时,所有测试在同一进程中运行,工作线程 ID 始终为 1。当不在测试上下文中运行时,此值为 undefined。
此属性可用于在并发测试文件之间分割资源(如数据库连接或服务器端口):
import { test } from 'node:test';
import { process } from 'node:process';
test('database operations', async (t) => {
// 可通过 context 获取 Worker ID
console.log(`Running in worker ${t.workerId}`);
// 或通过环境变量(在导入时可用)
const workerId = process.env.NODE_TEST_WORKER_ID;
// 使用 workerId 为每个 worker 分配独立的资源
});context.plan
History
Add the options parameter.
This function is no longer experimental.
context.plan(count, options?): void此函数用于设置测试内预期运行的断言和子测试的数量。如果运行的断言和子测试数量与预期计数不匹配,测试将失败。
注意:为了确保断言被追踪,必须使用
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 }); // 等待最多 2 秒以便断言完成。
const asyncActivity = () => {
setTimeout(() => {
t.assert.ok(true, 'Async assertion completed within the wait time');
}, 1000); // 在 1 秒后完成,在 2 秒的等待时间内。
};
asyncActivity(); // 测试将通过,因为断言及时完成。
});注意:如果指定了 wait 超时,它仅在测试函数执行完成后才开始倒计时。
context.runOnly(shouldRunOnlyTests): void<boolean>only
测试。如果 shouldRunOnlyTests 为真值,则测试上下文将仅运行设置了 only 选项的测试。否则,将运行所有测试。如果 Node.js 未使用 --test-only 命令行选项启动,则此函数为空操作。
test('top level test', (t) => {
// 测试上下文可设置为运行带有 'only' 选项的子测试。
t.runOnly(true);
return Promise.all([
t.test('this subtest is now skipped'),
t.test('this subtest is run', { only: true }),
]);
});当测试被中止时,可用于中止测试子任务。
test('top level test', async (t) => {
await fetch('some/uri', { signal: t.signal });
});context.skip(message?): void<string>此函数使测试的输出指示测试被跳过。如果提供了 message,则将其包含在输出中。调用 skip() 不会终止测试函数的执行。此函数不返回值。
test('top level test', (t) => {
// 如果测试包含额外逻辑,也请确保在此处返回。
t.skip('this is skipped');
});context.todo(message?): void<string>TODO
消息。此函数向测试的输出添加 TODO 指令。如果提供了 message,则将其包含在输出中。调用 todo() 不会终止测试函数的执行。此函数不返回值。
test('top level test', (t) => {
// 此测试被标记为 `TODO`
t.todo('this is a todo');
});context.test
History
Add a signal option.
Add a timeout option.
context.test(name?, options?, fn?): void<string>fn
的
name
属性,如果
fn
没有名称,则为
'<anonymous>'
。<Object>true
,它将并行运行所有子测试。如果为
false
,它将一次只运行一个测试。如果未指定,子测试将从其父级继承此值。
默认值:
null
。<boolean>only
测试,则此测试将被运行。否则,测试将被跳过。
默认值:
false
。<AbortSignal><number>Infinity
。<number>undefined
。TestContext
对象。如果测试使用回调,则回调函数作为第二个参数传递。
默认值:
一个空操作函数。<Promise>
一旦测试完成即 fulfilled 为
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?): void<Function>
|
<AsyncFunction><Object><Promise>
fulfilled 为
condition
返回的值。此方法轮询 condition 函数,直到该函数成功返回或操作超时。
类:SuiteContext
History
SuiteContext 的实例会被传递给每个套件函数,以便与测试运行器交互。但是,SuiteContext 构造函数并未作为 API 的一部分公开。
创建当前套件的测试文件的绝对路径。如果测试文件导入了生成套件的其他模块,导入的套件将返回根测试文件的路径。
套件及其每个祖先的名称,由 > 分隔。
套件的名称。
当测试被中止时,可用于中止测试子任务。
- 类型:
<boolean>
指示套件及其所有子测试是否已通过。
- 类型:
<number>
套件的尝试次数。该值从零开始,因此第一次尝试是 0,第二次尝试是 1,依此类推。此属性与 --test-rerun-failures 选项结合使用很有用,可确定当前运行的尝试次数。
context.diagnostic(message): void<string>输出诊断消息。这通常用于记录关于当前套件或其测试的信息。
test.describe('my suite', (suite) => {
suite.diagnostic('Suite diagnostic message');
});