On this page

稳定性:2 - 稳定

node:test 模块有助于创建 JavaScript 测试。 要访问它:

此模块仅在 node: 方案下可用。

通过 test 模块创建的测试由单个函数组成,该函数通过以下三种方式之一进行处理:

  1. 一个同步函数,如果抛出异常则视为失败,否则视为通过。
  2. 一个返回 Promise 的函数,如果 Promise 被拒绝则视为失败,如果 Promise 被履行则视为通过。
  3. 一个接收回调函数的函数。如果回调接收任何真值作为其第一个参数,则测试视为失败。如果将假值作为第一个参数传递给回调,则测试视为通过。如果测试函数接收回调函数并且还返回 Promise,则测试将失败。

以下示例说明了如何使用 test 模块编写测试。

test('同步通过测试', (t) => {
  // 此测试通过,因为它没有抛出异常。
  assert.strictEqual(1, 1);
});

test('同步失败测试', (t) => {
  // 此测试失败,因为它抛出了异常。
  assert.strictEqual(1, 2);
});

test('异步通过测试', async (t) => {
  // 此测试通过,因为 async 函数返回的 Promise 已结算且未被拒绝。
  assert.strictEqual(1, 1);
});

test('异步失败测试', async (t) => {
  // 此测试失败,因为 async 函数返回的 Promise 被拒绝。
  assert.strictEqual(1, 2);
});

test('使用 Promise 的失败测试', (t) => {
  // Promise 也可以直接使用。
  return new Promise((resolve, reject) => {
    setImmediate(() => {
      reject(new Error('这将导致测试失败'));
    });
  });
});

test('回调通过测试', (t, done) => {
  // done() 是回调函数。当 setImmediate() 运行时,它调用
  // done() 不带参数。
  setImmediate(done);
});

test('回调失败测试', (t, done) => {
  // 当 setImmediate() 运行时,done() 被传入一个 Error 对象且
  // 测试失败。
  setImmediate(() => {
    done(new Error('回调失败'));
  });
});

如果任何测试失败,进程退出代码将设置为 1

测试上下文的 test() 方法允许创建子测试。 它允许你以层次结构方式组织测试, 你可以在较大的测试内创建嵌套测试。 此方法的行为与顶级 test() 函数完全相同。 以下示例演示了创建一个 带有两个子测试的顶级测试。

test('顶级测试', async (t) => {
  await t.test('子测试 1', (t) => {
    assert.strictEqual(1, 1);
  });

  await t.test('子测试 2', (t) => {
    assert.strictEqual(2, 2);
  });
});

注意: beforeEachafterEach 钩子在 每个子测试执行之间触发。

在此示例中,使用 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 选项时,测试运行器将只运行尚未通过的测试。

套件和测试也可以使用 describe()it() 函数编写。describe()suite() 的别名,it()test() 的别名。

describe('某事物', () => {
  it('应该工作', () => {
    assert.strictEqual(1, 1);
  });

  it('应该没问题', () => {
    assert.strictEqual(2, 2);
  });

  describe('嵌套事物', () => {
    it('应该工作', () => {
      assert.strictEqual(3, 3);
    });
  });
});

describe()it()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');
});

这会翻转特定测试或套件的通过/失败报告:标记的测试 用例必须抛出异常才能通过,而标记的未抛出异常的测试用例 则失败。

在以下每种情况中,doTheThing() 未能返回 true,但由于 测试被标记为 expectFailure,它们会通过。

it.expectFailure('应该做这件事', () => {
  assert.strictEqual(doTheThing(), true);
});

it('应该做这件事', { expectFailure: true }, () => {
  assert.strictEqual(doTheThing(), true);
});

it('应该做这件事', { expectFailure: '功能尚未实现' }, () => {
  assert.strictEqual(doTheThing(), true);
});

如果 expectFailure 的值是 <RegExp> | <Function> | <Object> | <Error> 则仅当测试抛出匹配的值时才会通过。 请参阅 assert.throws 了解如何处理每种值类型。

以下每个测试都失败了,尽管 被标记为 expectFailure 因为失败不匹配特定的 预期 失败。

it('由于正则表达式不匹配而失败', {
  expectFailure: /expected message/,
}, () => {
  throw new Error('different message');
});

it('由于对象匹配器不匹配而失败', {
  expectFailure: { code: 'ERR_EXPECTED' },
}, () => {
  const err = new Error('boom');
  err.code = 'ERR_ACTUAL';
  throw err;
});

要为 expectFailure 提供原因和特定错误,请使用 { label, match }

it('应以特定错误和原因失败', {
  expectFailure: {
    label: '失败原因',
    match: /error message/,
  },
}, () => {
  assert.strictEqual(doTheThing(), true);
});

skip 和/或 todoexpectFailure 互斥,skiptodo 在同时应用时将“获胜”(skip 胜过两者,todo 胜过 expectFailure)。

这些测试将被跳过(且不运行):

it.expectFailure('应该做这件事', { skip: true }, () => {
  assert.strictEqual(doTheThing(), true);
});

it.skip('应该做这件事', { expectFailure: true }, () => {
  assert.strictEqual(doTheThing(), true);
});

这些测试将被标记为“todo”(静默错误):

it.expectFailure('应该做这件事', { todo: true }, () => {
  assert.strictEqual(doTheThing(), true);
});

it.todo('应该做这件事', { expectFailure: true }, () => {
  assert.strictEqual(doTheThing(), true);
});

如果 Node.js 使用 --test-only 命令行选项启动,或者测试隔离被禁用,则可以通过向应该运行的测试传递 only 选项来跳过除选定子集之外的所有测试。当设置了带有 only 选项的测试时,所有子测试也会运行。 如果套件设置了 only 选项,则运行套件内的所有测试,除非它有设置了 only 选项的后代,在这种情况下,只运行那些测试。

当在 test()/it() 中使用 [子测试][] 时,需要标记所有祖先测试带有 only 选项,以便仅运行选定的测试子集。

测试上下文的 runOnly() 方法可用于在子测试级别实现相同的行为。未执行的测试将从测试运行器输出中省略。

// 假设 Node.js 是使用 --test-only 命令行选项运行的。
// 套件的 'only' 选项已设置,因此运行这些测试。
test('此测试会运行', { only: true }, async (t) => {
  // 在此测试内,默认运行所有子测试。
  await t.test('运行子测试');

  // 可以更新测试上下文以运行带有 'only' 选项的子测试。
  t.runOnly(true);
  await t.test('此子测试现在被跳过');
  await t.test('此子测试会运行', { only: true });

  // 将上下文切换回以执行所有测试。
  t.runOnly(false);
  await t.test('此子测试现在会运行');

  // 显式不运行这些测试。
  await t.test('跳过的子测试 3', { only: false });
  await t.test('跳过的子测试 4', { skip: true });
});

// 未设置 'only' 选项,因此跳过此测试。
test('此测试不会运行', () => {
  // 此代码未运行。
  throw new Error('fail');
});

describe('一个套件', () => {
  // 已设置 'only' 选项,因此运行此测试。
  it('此测试会运行', { only: true }, () => {
    // 此代码已运行。
  });

  it('此测试不会运行', () => {
    // 此代码未运行。
    throw new Error('fail');
  });
});

describe.only('一个套件', () => {
  // 已设置 'only' 选项,因此运行此测试。
  it('此测试会运行', () => {
    // 此代码已运行。
  });

  it('此测试会运行', () => {
    // 此代码已运行。
  });
});

--test-name-pattern 命令行选项可用于仅运行名称与所提供模式匹配的测试,而 --test-skip-pattern 选项可用于跳过名称与所提供模式匹配的测试。测试名称模式会被解释为 JavaScript 正则表达式。--test-name-pattern--test-skip-pattern 选项可以指定多次,以运行嵌套测试。对于执行的每个测试,任何相应的测试钩子(如 beforeEach())也会运行。未执行的测试将从测试运行器输出中省略。

给定以下测试文件,使用 --test-name-pattern="test [1-3]" 选项启动 Node.js 将导致测试运行器执行 test 1test 2test 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 4Test 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,测试必须满足 两者 要求才能执行。

稳定性:1.0 - 早期开发

标签使用任意字符串标签为测试和套件添加注释。--experimental-test-tag-filter CLI 标志(或 run() 上的 testTagFilters 选项)会选择其标签集合包含每个 提供的过滤值的测试。

标签是将元数据编码到测试名称之外的一种替代方式。它们 适用于诸如子系统、速度等级、易波动性 或环境等横切维度,因为名称模式在这些场景下会比较脆弱。

import { describe, it } from 'node:test';

describe('database', { tags: ['db'] }, () => {
  it('reads a row');                                            // 标签:['db']
  it('writes a row', { tags: ['integration'] });                // 标签:['db', 'integration']
  it('reconnects after disconnect', { tags: ['flaky'] });       // 标签:['db', 'flaky']
});

标签值必须是非空字符串。标签匹配时不区分大小写; 规范形式为小写。同一 tags 数组内的重复项会按小写形式折叠, 并保留首次出现的声明顺序。

钩子(beforeafterbeforeEachafterEach)不会声明它们自己的 标签。它们作为其所属套件的一部分运行,而该套件承载 套件的标签。

在任何非空过滤条件下,未加标签的测试都会被排除,因为过滤条件 要求标签存在。

一旦测试函数完成执行,结果会尽快报告,同时保持测试的顺序。然而,测试函数可能会产生比测试本身存活时间更长的异步活动。测试运行器处理此类活动,但不会为了适应它而延迟测试结果的报告。

在以下示例中,一个测试完成时仍有两个 setImmediate() 操作未完成。第一个 setImmediate() 尝试创建一个新的子测试。因为父测试已经完成并输出了结果,新的子测试会立即标记为失败,并在稍后报告给 <TestsStream>

第二个 setImmediate() 创建一个 uncaughtException 事件。源自已完成测试的 uncaughtExceptionunhandledRejection 事件会被 test 模块标记为失败,并由 <TestsStream> 在顶层报告为诊断警告。

test('一个创建异步活动的测试', (t) => {
  setImmediate(() => {
    t.test('太晚创建的子测试', (t) => {
      throw new Error('error1');
    });
  });

  setImmediate(() => {
    throw new Error('error2');
  });

  // 测试在此行之后结束。
});

稳定性:1 - 实验性

Node.js 测试运行器支持通过传递 --watch 标志以监视模式运行:

在监视模式下,测试运行器将监视测试文件及其依赖项的更改。检测到更改时,测试运行器将重新运行受更改影响的测试。 测试运行器将持续运行,直到进程终止。

稳定性:1.0 - 早期开发

测试运行器支持指定一个模块,该模块将在所有测试执行之前进行评估,并可用于设置测试的全局状态或夹具。这对于准备多个测试所需的资源或设置共享状态很有用。

该模块可以导出以下任何内容:

  • 一个 globalSetup 函数,在所有测试开始前运行一次
  • 一个 globalTeardown 函数,在所有测试完成后运行一次

该模块在使用命令行运行测试时使用 --test-global-setup 标志指定。

// setup-module.js
async function globalSetup() {
  // 设置共享资源、状态或环境
  console.log('全局设置已执行');
  // 运行服务器、创建文件、准备数据库等。
}

async function globalTeardown() {
  // 清理资源、状态或环境
  console.log('全局清理已执行');
  // 关闭服务器、移除文件、断开数据库连接等。
}

module.exports = { globalSetup, globalTeardown };

如果全局设置函数抛出错误,将不会运行任何测试,进程将以非零退出码退出。 在这种情况下,全局清理函数不会被调用。

The Node.js test runner can be invoked from the command line by passing the --test flag:

By default, Node.js will run all files matching the following patterns:

  • **/*.test.{cjs,mjs,js}
  • **/*-test.{cjs,mjs,js}
  • **/*_test.{cjs,mjs,js}
  • **/test-*.{cjs,mjs,js}
  • **/test.{cjs,mjs,js}
  • **/test/**/*.{cjs,mjs,js}

Unless --no-strip-types is provided, the following additional patterns are also matched:

  • **/*.test.{cts,mts,ts}
  • **/*-test.{cts,mts,ts}
  • **/*_test.{cts,mts,ts}
  • **/test-*.{cts,mts,ts}
  • **/test.{cts,mts,ts}
  • **/test/**/*.{cts,mts,ts}

Alternatively, one or more glob patterns can be provided as the final arguments to the Node.js command, as shown below. Glob patterns follow the behavior of glob(7). On the command line, glob patterns should be enclosed in double quotes to prevent shell expansion, which can reduce portability issues across systems.

When randomization is enabled, the test runner prints the seed used for the run as a diagnostic message:

Use --test-random-seed=<number> to deterministically replay the same random order. Providing --test-random-seed also enables randomization, so --test-randomize is optional when a seed is provided:

In most test files, randomization takes effect automatically. One important exception is when subtests are awaited one by one. In this mode, each subtest starts only after the previous one completes, so the runner preserves declaration order instead of randomizing it.

Example: this runs in order, and is not randomized.

import test from 'node:test';

test('math', async (t) => {
  for (const name of ['adds', 'subtracts', 'multiplies']) {
    // Awaiting each subtest sequentially preserves declaration order.
    await t.test(name, async () => {});
  }
});

Using suite-style APIs such as describe()/it() or suite()/test() still allows randomization because sibling tests are queued together.

Example: this is still eligible for randomization.

import { describe, it } from 'node:test';

describe('math', () => {
  it('adds', () => {});
  it('subtracts', () => {});
  it('multiplies', () => {});
});

--test-randomize and --test-random-seed are not supported together with --watch mode.

Matching files are executed as test files. For more information about test file execution, see the Test runner execution model section.

Each test file is executed as though it were a regular script. That is, if the test file itself uses node:test to define tests, then all such tests are executed within a single application thread, regardless of the concurrency option value of test().

When process-level test isolation is disabled, each matching test file is imported into the test runner process. After all test files have been loaded, top-level tests are executed with a concurrency of 1. Because all test files run in the same context, tests may interact with each other in ways that are not possible when isolation is enabled. For example, if tests depend on global state, that state may be modified by tests originating from another file.

When running tests in process isolation mode (the default), spawned child processes inherit Node.js options from the parent process, including those specified in the [configuration file][]. However, certain flags are filtered out to enable correct test runner functionality:

  • --test - Prevents recursive test execution
  • --experimental-test-coverage - Managed by the test runner
  • --experimental-test-tag-filter - Filter values are validated by the parent process and re-sent to the child process
  • --watch - Watch mode is handled by the parent
  • --experimental-default-config-file - Configuration file loading is handled by the parent
  • --test-reporter - Reporting is managed by the parent process
  • --test-reporter-destination - Output destination is controlled by the parent
  • --experimental-config-file - Configuration file path is managed by the parent
  • --test-randomize - Randomization is managed by the parent process and propagated to child processes
  • --test-random-seed - Randomization seed is managed by the parent process and propagated to child processes

All other Node.js options from command-line arguments, environment variables, and configuration files are inherited by the child processes.

稳定性: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('这段代码永远不会被执行');
}
/* node:coverage enable */

也可以禁用指定行数的覆盖率。在指定行数之后,覆盖率将自动重新启用。如果未明确提供行数,则忽略单行。

/* node:coverage ignore next */
if (anAlwaysFalseCondition) { console.log('这段代码永远不会被执行'); }

/* node:coverage ignore next 3 */
if (anAlwaysFalseCondition) {
  console.log('这段代码永远不会被执行');
}
  • 此报告器不报告任何测试结果。
  • 理想情况下,此报告器应与另一个报告器一起使用。

node:test 模块支持通过顶层 mock 对象在测试期间进行模拟。以下示例创建一个函数的间谍,该函数将两个数字相加。然后使用间谍来断言该函数是否按预期被调用。

import assert from 'node:assert';
import { mock, test } from 'node:test';

test('spy 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('spy 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);
});

请参阅 MockTimers 类以获取方法和功能的完整列表。

这使得开发人员能够为依赖时间的功能编写更可靠和 可预测的测试。

下面的示例展示了如何模拟 setTimeout。 使用 .enable({ apis: ['setTimeout'] }); 它将模拟 node:timersnode: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);
});

日期实现也是 MockTimers 类的一部分。请参阅它以获取方法和功能的完整列表。

注意: 日期和计时器在一起模拟时是依赖的。这意味着 如果你同时模拟了 DatesetTimeout,推进时间也会 推进模拟的日期,因为它们模拟的是单个内部时钟。

下面的示例展示了如何模拟 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);
});

快照测试允许将任意值序列化为字符串值,并与一组已知良好值进行比较。这些已知良好值被称为快照,并存储在快照文件中。快照文件由测试运行器管理,但设计为人类可读以辅助调试。最佳实践是将快照文件与测试文件一起检入版本控制。

快照文件是通过使用 --test-update-snapshots 命令行标志启动 Node.js 生成的。每个测试文件都会生成一个单独的快照文件。默认情况下,快照文件与测试文件具有相同的名称,但带有 .snapshot 文件扩展名。此行为可以使用 snapshot.setResolveSnapshotPath() 函数进行配置。每个快照断言对应于快照文件中的一个导出。

下面显示了一个快照测试示例。第一次执行此测试时,它将失败,因为相应的快照文件不存在。

// test.js
suite('快照测试套件', () => {
  test('快照测试', (t) => {
    t.assert.snapshot({ value1: 1, value2: 2 });
    t.assert.snapshot(5);
  });
});

通过使用 --test-update-snapshots 运行测试文件来生成快照文件。测试应该通过,并且一个名为 test.js.snapshot 的文件将创建在与测试文件相同的目录中。快照文件的内容如下所示。每个快照由测试的全名和一个计数器标识,以区分同一测试中的快照。

exports[`快照测试套件 > 快照测试 1`] = `
{
  "value1": 1,
  "value2": 2
}
`;

exports[`快照测试套件 > 快照测试 2`] = `
5
`;

一旦创建了快照文件,再次运行测试而不使用 --test-update-snapshots 标志。现在测试应该通过。

node:test 模块支持传递 --test-reporter 标志,以便测试运行器使用特定的报告器。

支持以下内置报告器:

  • spec spec 报告器以人类可读格式输出测试结果。这是默认报告器。

  • tap tap 报告器以 TAP 格式输出测试结果。

  • dot dot 报告器以紧凑格式输出测试结果, 其中每个通过的测试由一个 . 表示, 每个失败的测试由一个 X 表示。

  • junit junit 报告器以 jUnit XML 格式输出测试结果

  • lcov lcov 报告器在与 --experimental-test-coverage 标志一起使用时输出测试覆盖率。

这些报告器的确切输出可能会在 Node.js 版本之间发生变化,不应以编程方式依赖。如果需要以编程方式访问测试运行器的输出,请使用由 <TestsStream> 发出的事件。

报告器可通过 node:test/reporters 模块获得:

使用 <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, `总行数:${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 `总行数:${totalLineCount}\n`;
        break;
      }
    }
  }
}

提供给 --test-reporter 的值应该是一个字符串,类似于 JavaScript 代码中 import() 使用的字符串,或者是提供给 --import 的值。

在以下示例中,spec 报告器将输出到 stdout, 而 dot 报告器将输出到 file.txt

当指定单个报告器时,除非明确提供了目标,否则目标将默认为 stdout

Attributes
options:<Object>
运行测试的配置选项。支持以下属性:
concurrency:<number> | <boolean>
如果提供的是数字, 那么将并行运行这么多个测试进程,其中每个进程对应一个测试文件。 如果为  true ,则会并行运行 os.availableParallelism() - 1 个测试文件。 如果为 false ,则一次只运行一个测试文件。 默认值: false
指定测试运行器使用的当前工作目录。 作为解析文件的基路径,行为类似于在该目录下[从命令行运行测试][]。  默认值: process.cwd()
files:<Array>
包含要运行的文件列表的数组。  默认值: 与[从命令行运行测试][]相同。
forceExit:<boolean>
配置测试运行器在所有已知测试执行完毕后退出进程,即使事件循环本来仍会保持活动状态。 默认值: false
globPatterns:<Array>
包含用于匹配测试文件的 glob 模式列表的数组。此选项不能与  files 同时使用。 默认值: 与[从命令行运行测试][]相同。
inspectPort:<number> | <Function>
设置测试子进程的检查器端口。 这可以是一个数字,也可以是一个不带参数并返回数字的函数。如果提供的是 nullish 值,则每个进程都会获得自己的端口,从主进程的  process.debugPort 递增。若将 isolation 选项设置为 'none' ,因为不会生成子进程,此选项将被忽略。 默认值: undefined
isolation:<string>
配置测试隔离类型。如果设置为  'process' ,每个测试文件都将在单独的子进程中运行。如果设置为 'none' ,所有测试文件都将在当前进程中运行。 默认值: 'process'
如果为 truthy,则测试上下文只会运行设置了  only 选项的测试
接受  TestsStream 实例的函数,可用于在任何测试运行之前设置监听器。 默认值: undefined
execArgv:<Array>
在生成子进程时传递给  node 可执行文件的一组 CLI 标志。 isolation'none ' 时此选项无效。 默认值: []
argv:<Array>
在生成子进程时传递给每个测试文件的一组 CLI 标志。 isolation'none' 时此选项无效。 默认值: []
允许中止正在进行中的测试执行。
testNamePatterns:<string> | <RegExp> | <Array>
一个字符串、RegExp 或 RegExp 数组, 可用于只运行名称与提供模式匹配的测试。 测试名称模式会被解释为 JavaScript 正则表达式。 对于执行的每个测试,任何对应的测试钩子(例如  beforeEach() )也会运行。 默认值: undefined
testSkipPatterns:<string> | <RegExp> | <Array>
一个字符串、RegExp 或 RegExp 数组, 可用于排除运行名称与提供模式匹配的测试。 测试名称模式会被解释为 JavaScript 正则表达式。 对于执行的每个测试,任何对应的测试钩子(例如  beforeEach() )也会运行。 默认值: undefined
testTagFilters:<string> | <string>
[] 一个标签名,或一个标签名数组, 用于按测试声明的标签筛选测试。测试必须包含列出的每个标签才能运行。等同于在命令行上传递  --experimental-test-tag-filter 。 参见 Test tags默认值: undefined
timeout:<number>
测试执行在多少毫秒后将失败。 如果未指定,子测试会从其父级继承此值。  默认值: Infinity
watch:<boolean>
是否以监视模式运行。 默认值: false
shard:<Object>
在特定分片中运行测试。 默认值: undefined
index:<number>
是介于 1 和  <total> 之间的正整数, 用于指定要运行的分片索引。此选项为 必需
total:<number>
是一个正整数,用于指定拆分测试文件的分片总数。此选项为  必需
randomize:<boolean>
随机化测试文件和队列中测试的执行顺序。 此选项不支持  watch: true默认值: false
randomSeed:<number>
用于随机化执行顺序的种子。如果设置了此 选项,则运行可以确定性地重放相同的随机顺序, 并且设置此选项也会启用随机化。该值必须是 介于  04294967295 之间的整数。 默认值: undefined
rerunFailuresFilePath:<string>
一个文件路径,测试运行器会 在其中存储测试状态,以便下次运行时仅重跑失败的测试。 更多信息请参见 [Rerunning failed tests][]。  默认值: undefined
coverage:<boolean>
启用 [代码覆盖率][] 收集。  默认值: false
coverageExcludeGlobs:<string> | <Array>
使用 glob 模式排除代码覆盖率中的特定文件, 该模式可以匹配绝对路径和相对路径。 仅当  coverage 设置为 true 时,此属性才适用。 如果同时提供了 coverageExcludeGlobscoverageIncludeGlobs , 文件必须同时满足 两者 条件才会包含在覆盖率报告中。 默认值: undefined
coverageIncludeGlobs:<string> | <Array>
使用 glob 模式将特定文件包含在代码覆盖率中, 该模式可以匹配绝对路径和相对路径。 仅当  coverage 设置为 true 时,此属性才适用。 如果同时提供了 coverageExcludeGlobscoverageIncludeGlobs , 文件必须同时满足 两者 条件才会包含在覆盖率报告中。 默认值: undefined
lineCoverage:<number>
要求覆盖行的最低百分比。如果代码 覆盖率未达到指定阈值,进程将以代码  1 退出。 默认值: 0
branchCoverage:<number>
要求覆盖分支的最低百分比。如果代码 覆盖率未达到指定阈值,进程将以代码  1 退出。 默认值: 0
functionCoverage:<number>
要求覆盖函数的最低百分比。如果代码 覆盖率未达到指定阈值,进程将以代码  1 退出。 默认值: 0
指定要传递给测试进程的环境变量。 此选项与  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);
Attributes
套件的名称,在报告测试结果时显示。 默认: fnname 属性;如果 fn 没有名称,则为 '<anonymous>'
options:<Object>
套件的可选配置项。 支持与  test([name][, options][, fn]) 相同的选项。
声明嵌套测试和套件的套件函数。该函数的第一个参数是一个  SuiteContext 对象。 默认: 无操作函数。

suite() 函数从 node:test 模块导入。

用于跳过套件的简写。这与 suite([name], { skip: true }[, fn]) 相同。

用于将套件标记为 TODO 的简写。这与 suite([name], { todo: true }[, fn]) 相同。

用于将套件标记为 only 的简写。这与 suite([name], { only: true }[, fn]) 相同。

Attributes
测试的名称,在报告测试结果时显示。 默认: fnname 属性;如果 fn 没有名称,则为 '<anonymous>'
options:<Object>
测试的配置选项。支持以下属性:
concurrency:<number> | <boolean>
如果提供一个数字,则会有那么多个测试异步运行(它们仍由单线程事件循环管理)。 如果为  true ,所有已安排的异步测试都会在该线程内并发运行。如果为 false ,一次只运行一个测试。 如果未指定,子测试会从其父级继承此值。 默认: false
如果为真值,则 预期测试会失败。如果提供非空字符串,则该字符串会在测试结果中显示为测试预期失败的原因。如果直接提供 <RegExp> | <Function> | <Object> | <Error> (而不是封装在  { match: … } 中),则只有在抛出的错误匹配时测试才会通过,行为与 assert.throws 一致。若要同时提供原因和验证,请传入一个包含 label (字符串)和 match (RegExp、Function、Object 或 Error)的对象。 默认: false
如果为真值,并且测试上下文配置为运行  only 测试,则会运行此测试。否则,测试会被跳过。 默认: false
允许中止正在进行中的测试。
如果为真值,则测试会被跳过。如果提供字符串,则该字符串会在测试结果中显示为跳过测试的原因。 默认: false
[] 与测试关联的字符串标签数组。 与  --experimental-test-tag-filter 一起用于筛选要运行的测试。标签会通过并集从套件继承到嵌套测试。参见 [测试标签][]。 默认: []
如果为真值,则测试会被标记为  TODO 。如果提供字符串,则该字符串会在测试结果中显示为测试为何为 TODO 的原因。 默认: false
timeout:<number>
测试在多少毫秒后失败。 如果未指定,子测试会从其父级继承此值。  默认: Infinity
预期在测试中运行的断言和子测试数量。 如果测试中实际运行的断言数量与计划中指定的数量不匹配,测试将失败。  默认: undefined
被测试的函数。该函数的第一个参数 是一个  TestContext 对象。如果测试使用回调,则回调函数作为第二个参数传入。 默认: 一个无操作函数。

test() 函数是从 test 模块导入的一个值。每次调用此函数都会向 <TestsStream> 报告一个测试。

传递给 fn 参数的 TestContext 对象可用于执行与当前测试相关的操作。示例包括跳过测试、添加 额外的诊断信息,或创建子测试。

test() 返回一个在测试完成后兑现的 Promise。 如果在套件内调用 test(),它会立即兑现。 顶层测试的返回值通常可以忽略。 但是,子测试的返回值应当使用,以防止父测试 过早结束并取消子测试,如以下示例所示。

test('顶层测试', async (t) => {
  // 如果删除下一行中的 'await',则下面子测试中的 setTimeout() 将会导致
  // 它的存活时间超过其父测试。一旦父测试完成,它将取消任何未完成的子测试。
  await t.test('运行时间较长的子测试', async (t) => {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, 1000);
    });
  });
});

timeout 选项可用于在测试执行时间超过 timeout 毫秒时使其失败。但是,这不是 一种可靠的取消测试机制,因为正在运行的测试可能会阻塞应用程序线程, 从而阻止计划中的取消。

A shorthand for skipping tests,
this is the same as test([name], { skip: true }[, fn]).

用于将测试标记为 TODO 的简写, 这与 test([name], { todo: true }[, fn]) 相同。

用于将测试标记为 only 的简写, 这与 test([name], { only: true }[, fn]) 相同。

suite() 的别名。

describe() 函数从 node:test 模块导入。

用于跳过套件的简写。这与 describe([name], { skip: true }[, fn]) 相同。

用于将套件标记为 TODO 的简写。这与 describe([name], { todo: true }[, fn]) 相同。

用于将套件标记为 only 的简写。这与 describe([name], { only: true }[, fn]) 相同。

test() 的别名。

it() 函数从 node:test 模块导入。

用于跳过测试的简写, 这与 相同。

用于将测试标记为 已跳过 的简写, 这与 跳过 相同。

用于将测试标记为 仅运行 的简写, 这与 相同。

  • 钩子 <Function> | <AsyncFunction> 钩子函数。 如果钩子使用回调, 则回调函数作为第二个参数传入。默认: 无操作函数。
  • 选项 <Object> 钩子的配置项。支持以下属性:
    • signal <AbortSignal> 允许中止正在进行中的钩子。
    • timeout <number> 钩子在多少毫秒后失败。 如果未指定,子测试会从其父级继承此值。 默认: 0。

此函数创建一个在执行套件之前运行的钩子。

describe('tests', async () => {
  before(() => console.log('即将运行一些测试'));
  it('is a subtest', () => {
    // 这里有一些相关断言
  });
});
Attributes
钩子函数。 如果钩子使用回调, 则回调函数作为第二个参数传入。 默认: 无操作函数。
options:<Object>
钩子的配置项。支持以下属性:
允许中止正在进行中的钩子。
timeout:<number>
钩子在多少毫秒后失败。 如果未指定,子测试会从其父级继承此值。  默认: Infinity

此函数会创建一个在执行套件之后运行的钩子。

describe('tests', async () => {
  after(() => console.log('测试运行结束'));
  it('is a subtest', () => {
    // 这里有一些相关断言
  });
});

注意: after 钩子保证会运行, 即使套件内的测试失败也是如此。

Attributes
Hook function. If the hook uses a callback, the callback function is passed as the second argument.  Default: No-op function.
options:<Object>
Hook options. Supports the following properties:
Allows aborting an in-progress hook.
timeout?:<number>
The number of milliseconds after which the hook fails. If not specified, subtests inherit this value from their parent.  Default: Infinity .

This function creates a hook that runs before each test in the current suite.

describe('tests', async () => {
  beforeEach(() => console.log('A test is about to run'));
  it('is a subtest', () => {
    // There are some relevant assertions here
  });
});
Attributes
Hook function. If the hook uses a callback, the callback function is passed as the second argument.  Default: A no-op function.
options:<Object>
Hook configuration options. Supports the following properties:
Allows the ongoing hook to be aborted.
timeout?:<number>
The number of milliseconds before the hook fails. If not specified, subtests inherit this value from their parent.  Default: Infinity .

This function creates a hook that runs after each test in the current test suite. The afterEach() hook runs even if a test fails.

describe('tests', async () => {
  afterEach(() => console.log('测试运行完成'));
  it('is a subtest', () => {
    // Related assertions go here
  });
});

一个对象,其方法用于配置当前进程中 node:assert 对象可用的断言。默认情况下,node:assert 中的方法和快照测试函数可用。

可以通过将通用配置代码放入通过 --import--require 预加载的模块中,将相同的配置应用于所有文件。

一个对象,其方法用于配置当前进程中的默认快照设置。可以通过将通用配置代码放入通过 --import--require 预加载的模块中,将相同的配置应用于所有文件。

此函数用于自定义测试运行器使用的默认序列化机制。默认情况下,测试运行器通过对提供的值调用 util.inspect 来序列化值。util.inspect 在循环结构和支持的数据类型方面确实存在限制。如果需要更强大的序列化机制,应使用此函数。

此函数用于自定义快照测试所使用的快照文件位置。默认情况下,快照文件的名称与入口文件相同,扩展名为 .snap

MockTracker 类用于检查或操纵通过 [node:test][] API 创建的 mock 的行为。

一个 getter,返回用于跟踪对 mock 调用的内部数组副本。数组中的每个条目都是一个具有以下属性的对象。

Attributes
arguments:<Array>
传递给 mock 函数的参数数组。
error:<any>
如果被 mock 的函数抛出了错误,则此属性包含抛出的值。 默认值: undefined
result:<any>
被 mock 函数返回的值。
stack:<Error>
一个  Error 对象,其堆栈可用于确定被 mock 函数调用的调用站点。
如果被 mock 的函数是构造函数,则此字段包含正在构造的类。否则,它将是  undefined
type:<any>
被 mock 函数的  this 值。

此函数返回此 mock 被调用的次数。此函数比检查 calls 更高效,因为 calls 是一个会创建内部调用跟踪数组副本的 getter。

此函数用于更改现有 mock 的行为。

以下示例使用 MockTracker 创建一个 mock 函数,调用该 mock 函数,然后将 mock 实现更改为另一个函数。

test('更改 mock 行为', (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);
});

此函数用于仅更改现有 mock 单次调用的行为。一旦 onCall 指定的调用发生,mock 将恢复为如果未调用 mockImplementationOnce() 时本应具有的行为。

以下示例使用 t.mock.fn() 创建一个 mock 函数,调用该 mock 函数,将下一次调用的 mock 实现更改为另一个函数,然后恢复其之前的行为。

test('只更改一次 mock 行为', (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);
});

稳定性:1.0 - 早期开发阶段

MockModuleContext 类用于操纵通过 MockTracker API 创建的模块 mock 的行为。

MockPropertyContext 类用于检查或操纵通过 MockTracker API 创建的属性 mock 的行为。

一个 getter,返回用于跟踪被 mock 属性访问(get/set)的内部数组副本。数组中的每一项都是一个具有以下属性的对象:

Attributes
'get''set' ,表示访问类型。
value:<any>
读取(对于  'get' )或写入(对于 'set' )的值。
stack:<Error>
一个  Error 对象,其堆栈可用于确定被 mock 函数的调用位置。

此函数返回属性被访问的次数。 此函数比检查 ctx.accesses.length 更高效,因为 ctx.accesses 是一个 getter,它会创建内部访问跟踪数组的副本。

此函数用于更改被 mock 属性 getter 返回的值。

此函数用于更改单次现有 mock 的行为。一旦发生 onAccess 调用,mock 将恢复为在未调用 mockImplementationOnce() 时本应使用的行为。

下面的示例使用 t.mock.property() 创建一个 mock 函数,调用该被 mock 属性,将下一次调用的 mock 实现更改为不同的值,然后恢复其先前行为。

test('更改一次 mock 行为', (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,那么“once”值会被 set 操作消费,并且被模拟的属性值将被更改为“once”值。如果你打算仅将“once”值用于 get 操作,这可能会导致意外行为。

MockTracker 类用于管理 mock 功能。test runner 模块提供一个顶层的 mock 导出,它是一个 MockTracker 实例。 每个测试也都有自己的 MockTracker 实例,可通过测试上下文的 mock 属性访问。

此函数用于创建 mock 函数。

下面的示例创建了一个在每次调用时都会递增计数器的 mock 函数。times 选项用于修改 mock 行为,使前两次调用每次将计数器增加 2 而不是 1。

test('模拟一个计数函数', (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。下面的 示例演示如何在现有对象方法上创建 mock。

test('监听一个对象方法', (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);
});
Attributes
specifier:<string> | <URL>
标识要模拟的模块的字符串。
options:<Object>
mock 模块的可选配置项。支持以下属性:
cache:<boolean>
如果为  false ,每次调用 require()import() 都会生成一个新的模拟模块。如果为 true ,后续调用将返回相同的模块 mock,并且该 mock 模块将被插入 CommonJS 缓存。 默认: false。
exports:<Object>
可选的 mock 导出。如果提供了  default 属性,它将被用作 mock 模块的默认导出。其他所有自有可枚举属性都作为具名导出使用。 此选项不能与 defaultExportnamedExports 一起使用。
defaultExport:<any>
可选值,用作 mock 模块的默认 导出。如果未提供该值,ESM mock 不包含默认导出。如果 mock 是 CommonJS 或内置模块,则此设置将用作  module.exports 的值。如果未提供该值,CJS 和内置 mock 将使用空对象作为 module.exports 的值。 此选项不能与 options.exports 一起使用。 此选项已弃用,并将在未来版本中移除。 请改用 options.exports.default
namedExports:<Object>
可选对象,其键和值用于 为 mock 模块创建具名导出。如果 mock 是 CommonJS 或 内置模块,这些值会被复制到  module.exports 上。因此,如果 mock 是通过具名导出和非对象默认导出创建的, 当作为 CJS 或内置模块使用时,mock 将抛出异常。 此选项不能与 options.exports 一起使用。 此选项已弃用,并将在未来版本中移除。 请改用 options.exports

此函数用于模拟 ECMAScript 模块、CommonJS 模块、JSON 模块和 Node.js 内置模块的导出。在 mock 之前对原始模块的任何引用都不会受到影响。要 启用模块 mock,Node.js 必须使用 --experimental-test-module-mocks 命令行标志运行。

注意module customization hooks 通过 同步 API 注册时会影响传递给 mock.modulespecifier 的解析。 通过 异步 API 注册的 customization hooks 当前会被忽略(因为 test runner 的 loader 是同步的,并且 Node 不支持 multi-chain/cross-chain loaders)。

下面的示例演示如何为模块创建 mock。

test('模拟内置模块在两种模块系统中的行为', async (t) => {
  // 为 'node:readline' 创建一个 mock,并带有一个名为 '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.foo(), 42);
  assert.strictEqual(cjsImpl.foo(), 42);

  mock.restore();

  // mock 已恢复,因此我们会拿回原始内置模块。
  esmImpl = await import('node:readline');
  cjsImpl = require('node:readline');

  assert.strictEqual(typeof esmImpl.cursorTo, 'function');
  assert.strictEqual(typeof cjsImpl.cursorTo, 'function');
  assert.strictEqual(esmImpl.foo, undefined);
  assert.strictEqual(cjsImpl.foo, undefined);
});

在对象上的某个属性值创建 mock。这使你可以跟踪并控制对特定属性的访问, 包括它被读取(getter)或写入(setter)的次数,并在 mock 后恢复原始值。

test('模拟一个属性值', (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);
});

每个测试完成后,都会在测试上下文的 MockTracker 上调用此函数。如果大量使用全局 MockTracker,建议手动调用此函数。

Mock timers 是软件测试中常用的一种技术,用于模拟和控制定时器(例如 setIntervalsetTimeout)的行为,而无需实际等待指定的时间间隔。

MockTimers 也可以模拟 Date 对象。

MockTracker 提供一个顶层的 timers 导出,它是一个 MockTimers 实例。

Attributes
enableOptions:<Object>
可选配置项,用于启用定时器模拟。支持以下属性:
apis:<Array>
一个可选数组,包含要模拟的定时器。 当前支持的定时器值有  'setInterval''setTimeout''setImmediate''Date'默认值: ['setInterval', 'setTimeout', 'setImmediate', 'Date'] 。 如果未提供数组,则默认模拟所有与时间相关的 API( 'setInterval''clearInterval''setTimeout''clearTimeout''setImmediate''clearImmediate''Date' )。
一个可选的数字或 Date 对象,表示用于  Date.now() 值的初始时间(以毫秒为单位)。 默认值: 0

注意: 当你为某个特定定时器启用模拟时,其关联的清除函数也会被隐式模拟。

注意: 模拟 Date 会影响 mock timers 的行为,因为它们使用的是同一个内部时钟。

不设置初始时间的使用示例:

import { mock } from 'node:test';
mock.timers.enable({ apis: ['setInterval'] });

上面的示例启用了对 setInterval 定时器的模拟,并隐式模拟了 clearInterval 函数。来自 node:timersnode:timers/promisesglobalThissetIntervalclearInterval 函数都会被模拟。

设置初始时间的使用示例

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:timersnode:timers/promisesglobalThissetIntervalclearIntervalsetTimeoutclearTimeoutsetImmediateclearImmediate 函数都会被模拟。全局 Date 对象也会被模拟。

注意: 每个测试完成后,都会在测试上下文的 MockTracker 上调用此函数。

import { mock } from 'node:test';
mock.timers.reset();
Attributes
milliseconds:<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);
});

如果在启用模拟时也将 Date 设置为被模拟,那么使用 .tick 推进时间时,也会推进之后创建的任何 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);
});

如前所述,来自定时器的所有清除函数(clearTimeoutclearIntervalclearImmediate)都会被隐式模拟。请看下面使用 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 已被清除,所以 mock 函数永远不会被调用
  assert.strictEqual(fn.mock.callCount(), 0);
});

一旦启用了定时器模拟,node:timersnode: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));
  }
});

下面的示例会立即触发所有待处理的定时器, 从而使它们无需延迟即可执行。

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() 函数专门用于在定时器模拟上下文中触发定时器。 它对真实系统时钟或模拟环境之外的实际定时器没有任何影响。

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);
});

Date 和定时器对象是相互依赖的。如果你使用 setTime() 将当前时间传递给一个被模拟的 Date 对象,那么使用 setTimeoutsetInterval 设置的定时器将不会受到影响。

但是,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, []);
  // Date 已推进,但定时器并未移动
  assert.strictEqual(Date.now(), 12000);
});

成功调用 run() 方法将返回一个新的 <TestsStream> 对象,流式传输一系列代表测试执行的事件。 TestsStream 将按测试定义的顺序发出事件。

某些事件保证按测试定义的顺序发出,而其他事件则按测试执行的顺序发出。

下表按作用域总结了所有事件。

测试作用域事件会在每个测试或套件中发出一次。它们中的大多数成对出现:一个按声明顺序的事件,会被缓冲,以便事件按测试定义的顺序发出;以及一个或多个对应的按执行顺序的事件,会在测试执行时立即发出。

声明顺序(缓冲)执行顺序(立即)
'test:start''test:enqueue' 后跟 'test:dequeue'
'test:pass''test:complete' (details.passedtrue)
'test:fail''test:complete' (details.passedfalse)
'test:plan'
'test:diagnostic'
'test:log'

'test:log' 特意只按执行顺序发出:它是 'test:diagnostic' 的缓冲报告的实时对应项。

文件作用域和全局事件总是会立即按执行顺序发出。

文件作用域事件会在每个测试文件中发出一次:

事件说明
'test:stderr'仅在传入 --test 标志时发出。
'test:stdout'仅在传入 --test 标志时发出。
'test:summary'每个文件一次,仅在使用进程隔离时发出。

全局事件会在每次测试运行中发出一次:

事件说明
'test:summary'最终的累计摘要。
'test:coverage'仅在启用代码覆盖率时。
'test:interrupted'仅在运行接收到 SIGINT 时。
'test:watch:drained'仅限监视模式。
'test:watch:restarted'仅限监视模式。

根测试还会在运行结束时发出 'test:plan''test:diagnostic' 事件,以报告运行级别的总计。

当启用代码覆盖率且所有测试完成后发出。

当测试完成执行时发出。 此事件的发出顺序与测试定义的顺序不一致。 对应的声明顺序事件是 'test:pass''test:fail'

当测试出队时发出,即在执行之前。 此事件不保证按测试定义的顺序发出。对应的声明顺序事件是 'test:start'

当调用 context.diagnostic 时发出。 此事件保证按测试定义的顺序发出。

当测试入队等待执行时发出。

当测试失败时发出。 此事件保证按测试定义的顺序发出。 对应的执行顺序事件是 'test:complete'

当测试运行器被 SIGINT 信号中断时发出(例如,按下 Ctrl+C 时)。该事件包含有关中断时正在运行的测试的信息。

当使用进程隔离(默认)时,测试名称将是文件路径,因为父运行器只知道文件级别的测试。当使用 --test-isolation=none 时,将显示实际的测试名称。

当调用 context.log 时触发。不同于 'test:diagnostic', 此事件会立即按测试执行顺序触发,因此适合用于以无缓冲方式渲染测试输出的报告器。

当测试通过时发出。 此事件保证按测试定义的顺序发出。 对应的执行顺序事件是 'test:complete'

当给定测试的所有子测试完成后发出。 此事件保证按测试定义的顺序发出。

当测试开始报告其自身及其子测试的状态时发出。 此事件保证按测试定义的顺序发出。 对应的执行顺序事件是 'test:dequeue'

当运行中的测试写入 stderr 时发出。 仅当传递了 --test 标志时才会发出此事件。 此事件不保证按测试定义的顺序发出。

当运行中的测试写入 stdout 时发出。 仅当传递了 --test 标志时才会发出此事件。 此事件不保证按测试定义的顺序发出。

当测试运行完成时发出。此事件包含与完成的测试运行相关的指标,有助于确定测试运行是通过还是失败。如果使用进程级测试隔离,除了最终的累计摘要外,还会为每个测试文件生成一个 'test:summary' 事件。

  • 返回值:<TestContext> | <SuiteContext> | <undefined>

返回与当前正在执行的测试或套件关联的 TestContextSuiteContext 对象,如果在测试或套件之外调用,则返回 undefined。此函数可用于从测试或套件函数内部或其中的任何异步操作中访问上下文信息。

import { getTestContext } from 'node:test';

test('示例测试', async () => {
  const ctx = getTestContext();
  console.log(`正在运行测试:${ctx.name}`);
});

describe('示例套件', () => {
  const ctx = getTestContext();
  console.log(`正在运行套件:${ctx.name}`);
});

当从测试中调用时,返回 TestContext。 当从套件中调用时,返回 SuiteContext

如果在测试或套件之外调用(例如,在模块的顶层或在执行完成后的 setTimeout 回调中),此函数返回 undefined

当从钩子(before、beforeEach、after、afterEach)内部调用时,此函数返回与该钩子关联的测试或套件的上下文。

测试运行器通过 Node.js diagnostics_channel 模块发布测试执行事件,使集成与 OpenTelemetry 等可观测性工具成为可能,而无需对测试运行器本身进行更改。

Attributes
测试名称。
nesting:<number>
测试的嵌套级别。
测试文件的路径,或在 REPL 中运行时为  undefined
测试类型。 'test''suite'

当测试或套件开始执行时发出。测试的范围包括其所有 before、beforeEach 和 afterEach 钩子以及测试主体。

Attributes
测试名称。
nesting:<number>
测试的嵌套级别。
测试文件的路径,或在 REPL 中运行时为  undefined
测试类型。 'test''suite'

当测试或套件完成执行时发出。

Attributes
测试名称。
nesting:<number>
测试的嵌套级别。
测试文件的路径,或在 REPL 中运行时为  undefined
测试类型。 'test''suite'
error:<Error>
抛出的错误。

当测试或套件抛出错误时发出。

import dc from 'node:diagnostics_channel';
import { AsyncLocalStorage } from 'node:async_hooks';

const testStorage = new AsyncLocalStorage();
const testChannel = dc.tracingChannel('node.test');

// 将上下文绑定到测试执行 — 返回的值成为存储
testChannel.start.bindStore(testStorage, (data) => {
  return { testName: data.name, startTime: Date.now() };
});

// 可选地处理错误和清理
testChannel.error.subscribe((data) => {
  const store = testStorage.getStore();
  console.log(`测试 "${data.name}" 在 ${Date.now() - store.startTime}ms 后失败`);
});

testChannel.end.subscribe((data) => {
  const store = testStorage.getStore();
  console.log(`测试 "${data.name}" 在 ${Date.now() - store.startTime}ms 内完成`);
});

使用 bindStore() 时,将自动将提供的上下文传播到测试函数和测试中的所有异步操作,而无需在测试代码中进行任何其他检测。

TestContext 的实例会传递给每个测试函数,以便与测试运行器交互。但是,TestContext 构造函数并未作为 API 的一部分暴露。

This function is used to create a hook that runs before the current test's subtests.

此函数用于创建一个钩子,在当前测试的每个子测试之前运行。

test('顶级测试', async (t) => {
  t.beforeEach((t) => t.diagnostic(`即将运行 ${t.name}`));
  await t.test(
    '这是一个子测试',
    (t) => {
      // 在此处进行相关的断言
    },
  );
});

此函数用于创建一个钩子,在当前测试完成后运行。

test('顶级测试', (t) => {
  t.after((t) => t.diagnostic(`已完成运行 ${t.name}`));
  // 此处进行相关的断言
});

This function is used to create a hook that runs after each child test of the current test.

test('顶级测试', async (t) => {
  t.afterEach((t) => t.diagnostic(`已完成运行 ${t.name}`));
  await t.test(
    '这是一个子测试',
    (t) => {
      // 此处进行相关的断言
    },
  );
});
Attributes
value:<any>
要序列化为字符串的值。如果 Node.js 是使用  --test-update-snapshots 标志启动的,则序列化的值将写入 path 。否则,序列化的值将与现有快照文件的内容进行比较。
写入序列化  value 的文件。
options:<Object>
可选配置选项。支持以下属性:
serializers:<Array>
用于将  value 序列化为字符串的同步函数数组。 value 作为唯一参数传递给第一个序列化器函数。每个序列化器的返回值作为输入传递给下一个序列化器。一旦所有序列化器运行完毕,结果值将被强制转换为字符串。 默认值: 如果未提供序列化器,则使用测试运行器的默认序列化器。

此函数将 value 序列化并将其写入由 path 指定的文件。

test('使用默认序列化的快照测试', (t) => {
  t.assert.fileSnapshot({ value1: 1, value2: 2 }, './snapshots/snapshot.json');
});

此函数与 context.assert.snapshot() 的区别如下:

  • 快照文件路径由用户显式提供。
  • 每个快照文件仅限于单个快照值。
  • 测试运行器不执行额外的转义。

这些差异使得快照文件能够更好地支持诸如语法高亮之类的功能。

Attributes
value:<any>
要序列化为字符串的值。如果 Node.js 是使用  --test-update-snapshots 标志启动的,则序列化的值将写入快照文件。否则,序列化的值将与现有快照文件中的相应值进行比较。
options:<Object>
可选配置选项。支持以下属性:
serializers:<Array>
用于将  value 序列化为字符串的同步函数数组。 value 作为唯一参数传递给第一个序列化器函数。每个序列化器的返回值作为输入传递给下一个序列化器。一旦所有序列化器运行完毕,结果值将被强制转换为字符串。 默认值: 如果未提供序列化器,则使用测试运行器的默认序列化器。

此函数实现快照测试的断言。

test('使用默认序列化的快照测试', (t) => {
  t.assert.snapshot({ value1: 1, value2: 2 });
});

test('使用自定义序列化的快照测试', (t) => {
  t.assert.snapshot({ value3: 3, value4: 4 }, {
    serializers: [(value) => JSON.stringify(value)],
  });
});

此函数用于将诊断信息写入输出。任何诊断信息都包含在测试结果的末尾。此函数不返回值。

test('顶级测试', (t) => {
  t.diagnostic('一条诊断消息');
});

此函数用于向输出写入日志消息。与 context.diagnostic 不同,生成的 'test:log' 事件会立即发出,按照测试执行的顺序进行,而不是缓冲到测试报告结果时才发出。此函数不返回值。

test('top level test', (t) => {
  t.log('fetched user', { userId: 42 });
  t.log('retrying flaky endpoint', { attempt: 3 });
});

指示测试是否成功。

测试/用例的失败原因;已包装并可通过 context.error.cause 访问。

测试的尝试次数。此值基于零,因此第一次尝试是 0,第二次尝试是 1,依此类推。此属性与 --test-rerun-failures 选项结合使用很有用,可用于确定测试当前正在运行第几次尝试。

An array of lowercase tags after flattening, ordered by declaration order, including any tags inherited from ancestor suites. If the test has no tags, it is empty. See Test tags.

运行当前测试文件的工作线程的唯一标识符。此值源自 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('数据库操作', async (t) => {
  // 可通过 context 获取 Worker ID
  console.log(`正在 worker ${t.workerId} 中运行`);

  // 或通过环境变量(在导入时可用)
  const workerId = process.env.NODE_TEST_WORKER_ID;
  // 使用 workerId 为每个 worker 分配独立的资源
});

此函数用于设置测试内预期运行的断言和子测试的数量。如果运行的断言和子测试数量与预期计数不匹配,测试将失败。

注意:为了确保断言被追踪,必须使用 t.assert 而不是直接使用 assert

test('顶级测试', (t) => {
  t.plan(2);
  t.assert.ok('some relevant assertion here');
  t.test('subtest', () => {});
});

当使用异步代码时,plan 函数可用于确保运行正确数量的断言:

test('使用流进行计划', (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('wait 为 2000 的计划会等待异步断言', (t) => {
  t.plan(1, { wait: 2000 }); // 最多等待 2 秒以便断言完成。

  const asyncActivity = () => {
    setTimeout(() => {
      t.assert.ok(true, '异步断言在等待时间内完成');
    }, 1000); // 在 1 秒后完成,在 2 秒的等待时间内。
  };

  asyncActivity(); // 测试将通过,因为断言及时完成。
});

注意:如果指定了 wait 超时,它仅在测试函数执行完成后才开始倒计时。

If shouldRunOnlyTests is truthy, the test context will only run tests that are set with the only option. Otherwise, all tests will run. If Node.js was not started with the --test-only command-line option, this function is a no-op.

test('顶级测试', (t) => {
  // 测试上下文可设置为运行带有 'only' 选项的子测试。
  t.runOnly(true);
  return Promise.all([
    t.test('此子测试现在将被跳过'),
    t.test('此子测试将运行', { only: true }),
  ]);
});

当测试被中止时,可用于中止测试子任务。

test('顶级测试', async (t) => {
  await fetch('some/uri', { signal: t.signal });
});

此函数会让测试输出表明测试已被跳过。如果提供了 message,则会将其包含在输出中。调用 skip() 不会终止测试函数的执行。此函数不返回值。

test('顶级测试', (t) => {
  // 如果测试包含额外逻辑,也请确保在此处返回。
  t.skip('此处被跳过');
});

此函数向测试的输出添加 TODO 指令。如果提供了 message,则将其包含在输出中。调用 todo() 不会终止测试函数的执行。此函数不返回值。

test('顶级测试', (t) => {
  // 此测试被标记为 `TODO`
  t.todo('这是一个待办事项');
});

此函数用于在当前测试下创建子测试。此函数的行为与顶层 test() 函数相同。

test('顶级测试', async (t) => {
  await t.test(
    '这是一个子测试',
    { only: false, skip: false, concurrency: 1, todo: false, plan: 1 },
    (t) => {
      t.assert.ok('这里有一些相关的断言');
    },
  );
});

This method polls the condition function until it completes successfully or the operation times out.

SuiteContext 的实例会被传递给每个套件函数,以便与测试运行器交互。但是,SuiteContext 构造函数并未作为 API 的一部分公开。

当测试被中止时,可用于中止测试子任务。

指示套件及其所有子测试是否已通过。

套件的尝试次数。该值从零开始,因此第一次尝试是 0,第二次尝试是 1,依此类推。此属性与 --test-rerun-failures 选项结合使用很有用,可确定当前运行的尝试次数。

输出诊断消息。这通常用于记录关于当前套件或其测试的信息。

test.describe('my suite', (suite) => {
  suite.diagnostic('套件诊断消息');
});

向输出写入日志消息。生成的 'test:log' 事件会立即发出,顺序与测试执行顺序一致。

test.describe('my suite', (suite) => {
  suite.log('套件日志消息');
});