On this page

测试运行器

History
Source Code: lib/test.js

稳定性: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');
});

预期测试失败

History

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

在以下每种情况中,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,测试必须满足 两者 要求才能执行。

测试标签

History

稳定性:1.0 - 早期开发

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

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

test()it()suite()describe() 任一函数上传入 tags 数组。 标签通过并集从套件继承到其子测试——套件中带有 ['db'] 标签的测试如果声明了自己的 tags: ['integration'] ,则实际上同时拥有这两个标签。

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']
});
const { describe, it } = require('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)不会声明它们自己的 标签。它们作为其所属套件的一部分运行,而该套件承载 套件的标签。

每个 --experimental-test-tag-filter 值都是一个字面标签名。一个 测试只有在其标签集合包含该名称时才会运行。该标志可以 多次指定;测试必须匹配 每个 过滤条件才能运行。run()[] 上的 testTagFilters 数组也同样适用。过滤器不区分大小写,并与 --test-name-pattern--test-skip-pattern.only 过滤进行 AND 组合。

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

TestContext 对象通过 context.tags 将测试的标签作为一个冻结数组暴露, 因此测试可以根据自己的元数据进行分支。

违反上述验证规则的标签值会在注册位置抛出 ERR_INVALID_ARG_VALUE,且发生在任何测试运行之前。 非数组的 tags 值会抛出 ERR_INVALID_ARG_TYPE

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

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

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

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

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

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

监视模式

History

稳定性:1 - 实验性

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

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

全局设置和清理

History

稳定性:1.0 - 早期开发

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

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

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

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

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

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

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

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

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

可以通过传递 --test 标志从命令行调用 Node.js 测试运行器:

默认情况下,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 展开,从而减少跨系统的可移植性问题。

随机化测试执行顺序

History

稳定性:1.0 - 早期开发

测试运行器可以随机化执行顺序,以帮助检测依赖顺序的测试。启用后,运行器会随机化发现的文件以及每个文件中排队的测试。使用 --test-randomize 可启用此模式。

启用随机化后,测试运行器会将本次运行所使用的种子打印为诊断消息:

使用 --test-random-seed=<number> 可以确定性地重放相同的随机顺序。提供 --test-random-seed 也会启用随机化,因此提供种子时,--test-randomize 是可选的:

在大多数测试文件中,随机化会自动生效。一个重要的例外是逐个等待子测试的情况。在此模式下,每个子测试只有在前一个子测试完成后才会启动,因此运行器会保留声明顺序,而不会将其随机化。

示例:以下代码按顺序运行,不会进行随机化。

import test from 'node:test';

test('math', async (t) => {
  for (const name of ['adds', 'subtracts', 'multiplies']) {
    // 依次等待每个子测试会保留声明顺序。
    await t.test(name, async () => {});
  }
});
const test = require('node:test');

test('math', async (t) => {
  for (const name of ['adds', 'subtracts', 'multiplies']) {
    // 依次等待每个子测试会保留声明顺序。
    await t.test(name, async () => {});
  }
});

使用 describe()/it()suite()/test() 等套件风格的 API 仍然支持随机化,因为同级测试会一起排队。

示例:以下代码仍然可以进行随机化。

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

describe('math', () => {
  it('adds', () => {});
  it('subtracts', () => {});
  it('multiplies', () => {});
});
const { describe, it } = require('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 - 由测试运行器管理
  • --experimental-test-tag-filter - 过滤值由父进程验证后重新发送给子进程
  • --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('这段代码永远不会被执行');
}
/* node:coverage enable */

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

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

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

tap 和 spec 报告器将打印覆盖率统计信息的摘要。还有一个 lcov 报告器,它将生成一个 lcov 文件,可用作更详细的覆盖率报告。

  • 此报告器不报告任何测试结果。
  • 理想情况下,此报告器应与另一个报告器一起使用。

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

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

test('监视函数', () => {
  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();
});
const assert = require('node:assert');
const { mock, test } = require('node:test');

test('监视函数', () => {
  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('监视对象方法', (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);
});

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

请参阅 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('模拟 setTimeout,使其同步执行而无需实际等待', () => {
  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();
});
const assert = require('node:assert');
const { mock, test } = require('node:test');

test('模拟 setTimeout,使其同步执行而无需实际等待', () => {
  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('模拟 setTimeout,使其同步执行而无需实际等待', (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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('模拟 setTimeout,使其同步执行而无需实际等待', (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 类的一部分。请参阅它以获取方法和功能的完整列表。

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

下面的示例展示了如何模拟 Date 对象并获取当前 Date.now() 值。

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

test('模拟 Date 对象', (context) => {
  // 可选地选择要模拟的内容
  context.mock.timers.enable({ apis: ['Date'] });
  // 如果未指定,初始日期将基于 UNIX 纪元的 0
  assert.strictEqual(Date.now(), 0);

  // 推进时间也会推进日期
  context.mock.timers.tick(9999);
  assert.strictEqual(Date.now(), 9999);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('模拟 Date 对象', (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('模拟具有初始时间的 Date 对象', (context) => {
  // 可选地选择要模拟的内容
  context.mock.timers.enable({ apis: ['Date'], now: 100 });
  assert.strictEqual(Date.now(), 100);

  // 推进时间也会推进日期
  context.mock.timers.tick(200);
  assert.strictEqual(Date.now(), 300);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('模拟具有初始时间的 Date 对象', (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('设置日期对象的时间', (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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('设置日期对象的时间', (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 不会执行计时器', (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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('setTime 不会执行计时器', (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('随着 setTime 推进执行计时器', (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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('随着 setTime 推进执行计时器', (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 模块获得:

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

export default customReporter;
const { Transform } = require('node:stream');

const customReporter = new Transform({
  writableObjectMode: true,
  transform(event, encoding, callback) {
    switch (event.type) {
      case 'test:dequeue':
        callback(null, `test ${event.data.name} dequeued`);
        break;
      case 'test:enqueue':
        callback(null, `test ${event.data.name} enqueued`);
        break;
      case 'test:watch:drained':
        callback(null, 'test watch queue drained');
        break;
      case 'test:watch:restarted':
        callback(null, 'test watch restarted due to file change');
        break;
      case 'test:start':
        callback(null, `test ${event.data.name} started`);
        break;
      case 'test:pass':
        callback(null, `test ${event.data.name} passed`);
        break;
      case 'test:fail':
        callback(null, `test ${event.data.name} failed`);
        break;
      case 'test:plan':
        callback(null, 'test plan');
        break;
      case 'test:diagnostic':
      case 'test:stderr':
      case 'test:stdout':
        callback(null, event.data.message);
        break;
      case 'test:coverage': {
        const { totalLineCount } = event.data.summary.totals;
        callback(null, `总行数:${totalLineCount}\n`);
        break;
      }
    }
  },
});

module.exports = customReporter;

使用生成器函数的自定义报告器示例:

export default async function * customReporter(source) {
  for await (const event of source) {
    switch (event.type) {
      case 'test:dequeue':
        yield `test ${event.data.name} dequeued\n`;
        break;
      case 'test:enqueue':
        yield `test ${event.data.name} enqueued\n`;
        break;
      case 'test:watch:drained':
        yield 'test watch queue drained\n';
        break;
      case 'test:watch:restarted':
        yield 'test watch restarted due to file change\n';
        break;
      case 'test:start':
        yield `test ${event.data.name} started\n`;
        break;
      case 'test:pass':
        yield `test ${event.data.name} passed\n`;
        break;
      case 'test:fail':
        yield `test ${event.data.name} failed\n`;
        break;
      case 'test:plan':
        yield 'test plan\n';
        break;
      case 'test:diagnostic':
      case 'test:stderr':
      case 'test:stdout':
        yield `${event.data.message}\n`;
        break;
      case 'test:coverage': {
        const { totalLineCount } = event.data.summary.totals;
        yield `总行数:${totalLineCount}\n`;
        break;
      }
    }
  }
}
module.exports = async function * customReporter(source) {
  for await (const event of source) {
    switch (event.type) {
      case 'test:dequeue':
        yield `test ${event.data.name} dequeued\n`;
        break;
      case 'test:enqueue':
        yield `test ${event.data.name} enqueued\n`;
        break;
      case 'test:watch:drained':
        yield 'test watch queue drained\n';
        break;
      case 'test:watch:restarted':
        yield 'test watch restarted due to file change\n';
        break;
      case 'test:start':
        yield `test ${event.data.name} started\n`;
        break;
      case 'test:pass':
        yield `test ${event.data.name} passed\n`;
        break;
      case 'test:fail':
        yield `test ${event.data.name} failed\n`;
        break;
      case 'test:plan':
        yield 'test plan\n';
        break;
      case 'test:diagnostic':
      case 'test:stderr':
      case 'test:stdout':
        yield `${event.data.message}\n`;
        break;
      case 'test:coverage': {
        const { totalLineCount } = event.data.summary.totals;
        yield `总行数:${totalLineCount}\n`;
        break;
      }
    }
  }
};

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

--test-reporter 标志可以指定多次,以多种格式报告测试结果。在这种情况下, 需要使用 --test-reporter-destination 为每个报告器指定一个目标。 目标可以是 stdoutstderr 或文件路径。 报告器和目标根据它们指定的顺序进行配对。

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

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

run(options?): void
Attributes
options:Object
运行测试的配置选项。支持以下属性:
concurrency:number | boolean
如果提供一个数字,则会并行运行相应数量的测试进程,其中每个进程对应一个测试文件。 如果为 true ,则会并行运行 os.availableParallelism() - 1 个测试文件。 如果为 false ,则一次只运行一个测试文件。 默认值: false
cwd:string
指定测试运行器使用的当前工作目录。 作为解析文件的基准路径,就像 从命令行运行测试 时从该目录运行一样。 默认值: process.cwd()
files:Array
要运行的文件列表。 默认值:从命令行运行测试 相同。
forceExit:boolean
配置测试运行器,使其在所有已知测试执行完毕后退出进程,即使事件循环原本仍会保持活动状态。 默认值: false
globPatterns:Array
用于匹配测试文件的 glob 模式列表。此选项不能与 files 同时使用。 默认值:从命令行运行测试 相同。
inspectPort:number | Function
设置测试子进程的检查器端口。 可以是一个数字,也可以是不接受参数并返回数字的函数。如果提供空值,则每个进程都会获得自己的端口,该端口从主进程的 process.debugPort 开始递增。如果将 isolation 选项设置为 'none' ,则会忽略此选项,因为不会生成子进程。 默认值: undefined
isolation:string
配置测试隔离类型。如果设置为 'process' ,则每个测试文件都会在单独的子进程中运行。如果设置为 'none' ,则所有测试文件都在当前进程中运行。 默认值: 'process'
only:boolean
如果为真,则测试上下文只运行设置了 only 选项的测试
setup:Function
接受 TestsStream 实例的函数,可用于在运行任何测试前设置监听器。 默认值: undefined
execArgv:Array
生成子进程时传递给 node 可执行文件的 CLI 标志数组。当 isolation'none' 时,此选项无效。 默认值: []
argv:Array
生成子进程时传递给每个测试文件的 CLI 标志数组。当 isolation'none' 时,此选项无效。 默认值: []
允许中止正在进行的测试执行。
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 。参见 测试标签默认值: 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
测试运行器用于存储测试状态的文件路径,以便在下次运行时只重新运行失败的测试。 有关更多信息,请参阅[重新运行失败的测试][]。 默认值: undefined
coverage:boolean
启用 代码覆盖率 收集。 默认值: false
coverageExcludeGlobs:string | Array
使用 glob 模式排除代码覆盖率中的特定文件,该模式可以匹配绝对文件路径和相对文件路径。 此属性仅在将 coverage 设置为 true 时适用。 如果同时提供 coverageExcludeGlobscoverageIncludeGlobs ,则文件必须同时满足这两个条件才能包含在覆盖率报告中。 默认值: undefined
coverageIncludeGlobs:string | Array
使用 glob 模式将特定文件包含在代码覆盖率中,该模式可以匹配绝对文件路径和相对文件路径。 此属性仅在将 coverage 设置为 true 时适用。 如果同时提供 coverageExcludeGlobscoverageIncludeGlobs ,则文件必须同时满足这两个条件才能包含在覆盖率报告中。 默认值: undefined
coverageIncludeAll:boolean
将测试运行期间从未加载的源文件包含在覆盖率报告中,并将其报告为覆盖率为零。候选文件会在 cwd 中搜索,并接受与报告其余部分相同的 coverageIncludeGlobscoverageExcludeGlobs 筛选。此属性仅在将 coverage 设置为 true 时适用。 默认值: false
lineCoverage:number
要求达到最低行覆盖率百分比。如果代码覆盖率未达到指定阈值,进程将以代码 1 退出。 默认值: 0
branchCoverage:number
要求达到最低分支覆盖率百分比。如果代码覆盖率未达到指定阈值,进程将以代码 1 退出。 默认值: 0
functionCoverage:number
要求达到最低函数覆盖率百分比。如果代码覆盖率未达到指定阈值,进程将以代码 1 退出。 默认值: 0
env: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);
const { tap } = require('node:test/reporters');
const { run } = require('node:test');
const path = require('node:path');

run({ files: [path.resolve('./tests/test.js')] })
 .on('test:fail', () => {
   process.exitCode = 1;
 })
 .compose(tap)
 .pipe(process.stdout);

测试套件

History
Attributes
name:string
套件的名称,在报告测试结果时显示。 默认: fnname 属性;如果 fn 没有名称,则为 '<anonymous>'
options:Object
套件的可选配置项。 支持与 test([name][, options][, fn]) 相同的选项。
声明嵌套测试和套件的套件函数。该函数的第一个参数是一个 SuiteContext 对象。 默认: 无操作函数。

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

跳过套件

History

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

仅运行此套件

History

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

仅运行此套件

History

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

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

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?): void

suite() 的别名。

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] 相同。

M

describe.only

History
describe.only(name?, options?, fn?): void

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

M

it

History
it(name?, options?, fn?): void

test() 的别名。

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

用于跳过测试的简写,
这与 [仅][it options] 相同。

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

仅运行(异步)

History

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

钩子

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

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

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

after

History
after(fn?, options?): void
Attributes
钩子函数。 如果钩子使用回调, 则回调函数作为第二个参数传入。 默认值: 无操作函数。
options:Object
钩子的配置项。支持以下属性:
允许中止正在进行的钩子。
timeout:number
钩子在多少毫秒后失败。 如果未指定,子测试会从其父级继承此值。 默认值: Infinity

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

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

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

M

beforeEach

History
beforeEach(fn?, options?): void
Attributes
钩子函数。 如果钩子使用回调,则回调函数会作为第二个参数传入。 默认值: 无操作函数。
options:Object
钩子选项。支持以下属性:
允许中止正在进行的钩子。
timeout:number
钩子失败前等待的毫秒数。 如果未指定,子测试会从其父测试继承此值。 默认值: Infinity

此函数创建一个在当前测试套件中的每个测试之前运行的钩子。

describe('tests', async () => {
  beforeEach(() => console.log('A test is about to run'));
  it('is a subtest', () => {
    // 此处有一些相关的断言
  });
});
M

afterEach

History
afterEach(fn?, options?): void
Attributes
The 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 hook in progress to be aborted.
timeout?:number
The number of milliseconds before the hook fails. If not specified, subtests will inherit this value from their parent test. Default: Infinity .

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

describe('tests', async () => {
  afterEach(() => console.log('Test run completed'));
  it('is a subtest', () => {
    // Write related assertions here
  });
});

断言

History

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

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

断言函数

History

使用给定的名称和函数定义一个新的断言函数。如果同名断言已存在,则会被替换。

快照

History

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

默认序列化器

History
Attributes
add:Array
作为快照测试默认序列化器使用的同步函数数组。

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

快照文件名

History
Attributes
用于计算快照文件位置的函数。 此函数接收测试文件的路径作为其唯一参数。如果测试未关联文件(例如在 REPL 中),则输入为 undefinedfn() 必须返回一个字符串,用于指定快照文件的位置。

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

类:MockTracker

History

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

调用记录

History

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

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

调用次数

History
  • 返回:integer 此 mock 被调用的次数。

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

重置实现

History
Attributes
implementation:Function | AsyncFunction
用作 mock 新实现的函数。

此函数用于更改现有 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);
});
M

ctx.mockImplementationOnce

History
ctx.mockImplementationOnce(implementation, onCall?): void
Attributes
implementation:Function | AsyncFunction
用作由 onCall 指定的调用编号的 mock 实现的函数。
onCall:integer
将使用 implementation 的调用编号。如果 指定的调用已经发生,则抛出异常。 默认值: 下一次调用的编号。

此函数用于仅更改现有 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);
});
M

ctx.resetCalls

History
ctx.resetCalls(): void

重置 mock 函数的调用历史。

M

ctx.restore

History
ctx.restore(): void

将 mock 函数实现重置为其原始行为。调用此函数后仍可继续使用 mock。

类:MockModuleContext

History

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

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

M

ctx.restore

History
ctx.restore(): void

重置被 mock 的模块实现。

类:MockPropertyContext

History

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

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

Attributes
type:string
'get''set' ,表示访问类型。
value:any
读取(对于 'get' )或写入(对于 'set' )的值。
stack:Error
一个 Error 对象,其堆栈可用于确定被 mock 函数的调用位置。
ctx.accessCount(): void
  • 返回:integer 属性被访问的次数(读取或写入)。

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

ctx.mockImplementation(value): void
Attributes
value:any
要设置为被 mock 属性值的新值。

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

ctx.mockImplementationOnce(value, onAccess?): void
Attributes
value:any
用作 onAccess 指定调用编号的 mock 实现的值。
onAccess:integer
将使用 value 的调用编号。如果 指定的调用已经发生,则会抛出异常。 默认: 下一次调用的编号。

此函数用于更改单次现有 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 操作,这可能会导致意外行为。

ctx.resetAccesses(): void

重置被模拟属性的访问历史。

ctx.restore(): void

将被模拟属性实现重置为其原始行为。调用此函数后,mock 仍然可以继续使用。

类:MockTracker

History

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

M

mock.fn

History
mock.fn(original?, implementation?, options?): void
Attributes
可选函数,用于在其基础上创建 mock。 默认: 一个空操作函数。
implementation:Function | AsyncFunction
可选函数,用作 original 的 mock 实现。这对于创建在指定次数内按一种方式运行,然后恢复为 original 行为的 mock 很有用。 默认:original 指定的函数。
options:Object
mock 函数的可选配置项。支持以下属性:
times:integer
mock 将使用 implementation 行为的次数。一旦 mock 函数被调用 times 次,它将自动恢复为 original 的行为。该值必须是正整数。 默认: Infinity

此函数用于创建 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);
});
M

mock.method

History
mock.method(): void

此函数是 MockTracker.method 的语法糖,其中 options.getter 被设为 true

M

mock.method

History
mock.method(object, methodName, implementation?, options?): void
Attributes
object:Object
要对其方法进行 mock 的对象。
methodName:string | symbol
object 上要 mock 的方法标识符。 如果 object[methodName] 不是函数,则会抛出错误。
implementation:Function | AsyncFunction
可选函数,用作 object[methodName] 的 mock 实现。 默认: object[methodName] 指定的原始方法。
options:Object
mock 方法的可选配置项。支持以下属性:
getter:boolean
如果为 trueobject[methodName] 将被视为 getter。 此选项不能与 setter 选项同时使用。 默认: false。
setter:boolean
如果为 trueobject[methodName] 将被视为 setter。 此选项不能与 getter 选项同时使用。 默认: false。
times:integer
mock 将使用 implementation 行为的次数。一旦被 mock 的方法被调用 times 次,它将自动恢复为原始行为。该值必须是正整数。 默认: Infinity

此函数用于在现有对象方法上创建 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);
});
M

mock.module

History
mock.module(specifier, options?): void

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

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

mock.property

History
mock.property(object, propertyName, value?): void
Attributes
object:Object
要对其值进行 mock 的对象。
propertyName:string | symbol
object 上要 mock 的属性标识符。
value:any
可选值,用作 object[propertyName] 的 mock 值。 默认: 原始属性值。

在对象上的某个属性值创建 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);
});
M

mock.reset

History
mock.reset(): void

此函数会恢复此前由此 MockTracker 创建的所有 mock 的默认行为,并将这些 mock 与 MockTracker 实例解除关联。一旦解除关联,这些 mock 仍然可以使用,但 MockTracker 实例将无法再重置它们的行为或 以其他方式与它们交互。

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

M

mock.restoreAll

History
mock.restoreAll(): void

此函数会恢复此前由此 MockTracker 创建的所有 mock 的默认行为。与 mock.reset() 不同,mock.restoreAll() 不会 将这些 mock 与 MockTracker 实例解除关联。

M

mock.setter

History
mock.setter(object, methodName, implementation?, options?): void

此函数是 MockTracker.method 的语法糖,其中 options.setter 被设为 true

类:MockTimers

History

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

MockTimers 也可以模拟 Date 对象。

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

timers.enable(enableOptions?): void

为指定的计时器启用计时器模拟。

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

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

注意: 模拟 Date 会影响模拟计时器的行为,因为它们使用相同的内部时钟。

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

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

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

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

import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: 1000 });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: 1000 });

使用初始 Date 对象的示例:

import { mock } from 'node:test';
mock.timers.enable({ apis: ['Date'], now: new Date() });
const { mock } = require('node:test');
mock.timers.enable({ apis: ['Date'], now: new Date() });

或者,如果调用不带参数的 mock.timers.enable()

所有计时器('setInterval''clearInterval''setTimeout''clearTimeout''setImmediate''clearImmediate')都将被模拟。来自 node:timersnode:timers/promisesglobalThissetIntervalclearIntervalsetTimeoutclearTimeoutsetImmediateclearImmediate 函数将被模拟。全局 Date 对象也将被模拟。

M

timers.reset

History
timers.reset(): void

此函数会恢复此前由该 MockTimers 实例创建的所有 mock 的默认行为,并解除这些 mock 与 MockTracker 实例之间的关联。

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

import { mock } from 'node:test';
mock.timers.reset();
const { mock } = require('node:test');
mock.timers.reset();
timers[Symbol.dispose](): void

调用 timers.reset()

M

timers.tick

History
timers.tick(milliseconds?): void

推进所有已模拟定时器的时间。

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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();
  context.mock.timers.enable({ apis: ['setTimeout'] });

  setTimeout(fn, 9999);
  assert.strictEqual(fn.mock.callCount(), 0);

  // 推进时间
  context.mock.timers.tick(9999);

  assert.strictEqual(fn.mock.callCount(), 1);
});

另外,.tick 可以多次调用

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

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();
  context.mock.timers.enable({ apis: ['setTimeout'] });
  const nineSecs = 9000;
  setTimeout(fn, nineSecs);

  const threeSeconds = 3000;
  context.mock.timers.tick(threeSeconds);
  context.mock.timers.tick(threeSeconds);
  context.mock.timers.tick(threeSeconds);

  assert.strictEqual(fn.mock.callCount(), 1);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();
  context.mock.timers.enable({ apis: ['setTimeout'] });
  const nineSecs = 9000;
  setTimeout(fn, nineSecs);

  const threeSeconds = 3000;
  context.mock.timers.tick(threeSeconds);
  context.mock.timers.tick(threeSeconds);
  context.mock.timers.tick(threeSeconds);

  assert.strictEqual(fn.mock.callCount(), 1);
});

如果在启用模拟时也将 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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();
  context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });

  setTimeout(fn, 9999);
  assert.strictEqual(fn.mock.callCount(), 0);
  assert.strictEqual(Date.now(), 0);

  // 推进时间
  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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
  const fn = context.mock.fn();

  // 可选地选择要模拟的内容
  context.mock.timers.enable({ apis: ['setTimeout'] });
  const 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);
});
const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimers = require('node:timers');
const nodeTimersPromises = require('node:timers/promises');

test('mocks setTimeout to be executed synchronously without having to actually wait for it', async (context) => {
  const globalTimeoutObjectSpy = context.mock.fn();
  const nodeTimerSpy = context.mock.fn();
  const nodeTimerPromiseSpy = context.mock.fn();

  // 可选地选择要模拟的内容
  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));
  }
});
const assert = require('node:assert');
const { test } = require('node:test');
const nodeTimersPromises = require('node:timers/promises');
test('should tick five times testing a real use case', async (context) => {
  context.mock.timers.enable({ apis: ['setInterval'] });

  const expectedIterations = 3;
  const interval = 1000;
  const startedAt = Date.now();
  async function run() {
    const times = [];
    for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
      times.push(time);
      if (times.length === expectedIterations) break;
    }
    return times;
  }

  const r = run();
  context.mock.timers.tick(interval);
  context.mock.timers.tick(interval);
  context.mock.timers.tick(interval);

  const timeResults = await r;
  assert.strictEqual(timeResults.length, expectedIterations);
  for (let it = 1; it < expectedIterations; it++) {
    assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
  }
});
M

timers.runAll

History
timers.runAll(): void

立即触发所有待处理的 mock 定时器。如果 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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('runAll functions following the given order', (context) => {
  context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
  const results = [];
  setTimeout(() => results.push(1), 9999);

  // 请注意,如果两个定时器具有相同的超时时间,
  // 其执行顺序是有保证的
  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() 函数专门用于在定时器模拟上下文中触发定时器。 它对真实系统时钟或模拟环境之外的实际定时器没有任何影响。

M

timers.setTime

History
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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('setTime replaces current time', (context) => {
  const now = Date.now();
  const setTime = 1000;
  // Date.now 未被模拟
  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);
});
const assert = require('node:assert');
const { test } = require('node:test');

test('runAll functions following the given order', (context) => {
  context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
  const results = [];
  setTimeout(() => results.push(1), 9999);

  assert.deepStrictEqual(results, []);
  context.mock.timers.setTime(12000);
  assert.deepStrictEqual(results, []);
  // 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' 事件,以报告运行级别的总计。

Attributes
data:Object
summary:Object
一个包含覆盖率报告的对象。
files:Array
一个包含各个文件覆盖率报告的数组。每个报告都是一个具有以下架构的对象:
path:string
文件的绝对路径。
totalLineCount:number
总行数。
totalBranchCount:number
总分支数。
totalFunctionCount:number
总函数数。
coveredLineCount:number
已覆盖的行数。
coveredBranchCount:number
已覆盖的分支数。
coveredFunctionCount:number
已覆盖的函数数。
coveredLinePercent:number
已覆盖行的百分比。
coveredBranchPercent:number
已覆盖分支的百分比。
coveredFunctionPercent:number
已覆盖函数的百分比。
functions:Array
一个代表函数覆盖率的函数数组。
name:string
函数名称。
line:number
函数定义所在的行号。
count:number
函数被调用的次数。
branches:Array
一个代表分支覆盖率的分支数组。
line:number
分支定义所在的行号。
count:number
分支被采用的次数。
lines:Array
一个代表行号及其被覆盖次数的行数组。
line:number
行号。
count:number
该行被覆盖的次数。
thresholds:Object
一个包含每种覆盖率类型是否达到阈值的对象。
function:number
函数覆盖率阈值。
branch:number
分支覆盖率阈值。
line:number
行覆盖率阈值。
totals:Object
一个包含所有文件覆盖率摘要的对象。
totalLineCount:number
总行数。
totalBranchCount:number
总分支数。
totalFunctionCount:number
总函数数。
coveredLineCount:number
已覆盖的行数。
coveredBranchCount:number
已覆盖的分支数。
coveredFunctionCount:number
已覆盖的函数数。
coveredLinePercent:number
已覆盖行的百分比。
coveredBranchPercent:number
已覆盖分支的百分比。
coveredFunctionPercent:number
已覆盖函数的百分比。
workingDirectory:string
开始代码覆盖率时的工作目录。如果测试更改了 Node.js 进程的工作目录,这对于显示相对路径名很有用。
nesting:number
测试的嵌套级别。

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

Attributes
data:Object
column:number | undefined
定义测试所在的列号,如果测试是通过 REPL 运行的,则为 undefined
details:Object
额外的执行元数据。
passed:boolean
测试是否通过。
duration_ms:number
测试持续时间,单位为毫秒。
如果测试未通过,则为一个封装了测试抛出错误的错误对象。
cause:Error
测试实际抛出的错误。
测试的类型,用于表示这是否是一个测试套件。
entryFile:string | undefined
作为触发此事件的子进程入口点执行的测试文件路径。 仅在测试使用进程隔离运行时存在。当测试定义在由入口文件导入的模块中时,可能与 file 不同。
测试文件的路径,如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号,如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
包含该测试的 testId ,顶层测试则为 undefined 。用于在同一嵌套层级的并发兄弟测试交错执行时,让自定义报告器跟踪继承关系。
tags:string[]
测试及其祖先套件上声明的、已展平并转为小写的标签,按声明顺序排列。未标记的测试为空。 参见 [Test tags][]。
testId:number
此测试实例的数字标识符,在测试文件的进程内唯一。 在同一测试实例的所有事件中保持一致,便于自定义报告器可靠关联。
testNumber:number
测试的序号。
当调用 context.todo 时存在。
当调用 context.skip 时存在。

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

Attributes
data:Object
column:number | undefined
定义测试的列号;如果测试是通过 REPL 运行的,则为 undefined
entryFile:string | undefined
作为触发此事件的子进程入口点而执行的测试文件路径。仅在测试使用进程隔离运行时存在。当测试定义在由入口文件导入的模块中时,可能与 file 不同。
测试文件路径;如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号;如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
所包含测试的 testId ;顶层测试为 undefined 。用于在同一嵌套层级的并发同级测试交错执行时,让自定义报告器跟踪继承关系。
tags:string[]
在测试及其祖先套件上声明的、展开后并小写化的标签,按声明顺序排列。未标记的测试为空。参见 [Test tags][]。
testId:number
此测试实例的数字标识符,在测试文件的进程内唯一。对于同一测试实例,在所有事件中保持一致,使自定义报告器能够可靠地关联。
type:string
测试类型。可以是 'suite''test'

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

Attributes
data:Object
column:number | undefined
定义测试的列号,或者如果测试是通过 REPL 运行的,则为 undefined
entryFile:string | undefined
作为触发此事件的子进程入口点执行的测试文件路径。 仅在测试使用进程隔离运行时存在。如果测试定义在入口文件导入的模块中,可能与 file 不同。
测试文件的路径,如果测试是通过 REPL 运行的,则为 undefined
定义测试的行号,或者如果测试是通过 REPL 运行的,则为 undefined
message:string
诊断消息。
nesting:number
测试的嵌套层级。
level:string
诊断消息的严重级别。 可能的值有:
'info':
:信息性消息。
'warn':
:警告。
'error':
:错误。

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

Attributes
data:Object
column:number | undefined
定义测试所在的列号;如果测试是通过 REPL 运行的,则为 undefined
entryFile:string | undefined
作为触发此事件的子进程入口点执行的测试文件路径。仅在测试以进程隔离方式运行时存在。若测试定义在由入口文件导入的模块中,则可能与 file 不同。
测试文件的路径;如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号;如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
外层测试的 testId ;顶层测试为 undefined 。当同一嵌套层级的并发同级测试交错执行时,可帮助自定义报告器跟踪来源关系。
tags:string[]
按声明顺序展开并转为小写后的标签,包含测试及其祖先套件上声明的标签。未标记的测试为空。另见 [Test tags][]。
testId:number
此测试实例的数值标识符,在该测试文件的进程内唯一。对同一测试实例的所有事件保持一致,便于自定义报告器可靠关联。
type:string
测试类型。为 'suite''test'

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

Attributes
data:Object
column:number | undefined
测试定义所在的列号,或者 如果测试是通过 REPL 运行的,则为 undefined
details:Object
额外的执行元数据。
duration_ms:number
测试持续的毫秒数。
error:Error
对测试抛出的错误进行包装的错误。
cause:Error
测试实际抛出的错误。
测试的类型,用于表示 这是否是一个测试套件。
attempt:number | undefined
测试运行的尝试次数, 仅在使用 --test-rerun-failures 标志时存在。
entryFile:string | undefined
作为发出此事件的子进程入口点 执行的测试文件路径。 仅在测试使用进程隔离运行时存在。当测试定义在由入口文件导入的模块中时,它可能与 file 不同。
测试文件的路径, 如果测试是通过 REPL 运行的,则为 undefined
测试定义所在的行号,或者 如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
包含该测试的 testId ,或者 对于顶层测试为 undefined 。当同一嵌套层级的并发同级测试交错执行时,允许自定义报告器跟踪其来源关系。
tags:string[]
在测试及其祖先套件上声明的、展开并转为小写的标签,按声明顺序排列。未打标签的测试为空。 参见 测试标签
testId:number
该测试实例的数字标识符,在测试文件的进程内唯一。 对同一测试实例的所有事件保持一致,从而使自定义报告器能够可靠地关联。
testNumber:number
测试的序号。
当调用 context.todo 时存在
当调用 context.skip 时存在

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

事件:'test:interrupted'

History
Attributes
data:Object
tests:Array
一个包含有关被中断测试信息的对象数组。
column:number | undefined
测试定义所在的列号,如果测试是通过 REPL 运行的,则为 undefined
测试文件的路径,如果测试是通过 REPL 运行的,则为 undefined
测试定义所在的行号,如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套级别。

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

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

事件:'test:log'

History
Attributes
data:Object
column:number | undefined
定义测试所在的列号;如果测试是通过 REPL 运行的,则为 undefined
data:any
传递给 context.log 的结构化载荷;如果未提供,则为 undefined 。测试运行器不会解释此值。
entryFile:string | undefined
作为触发此事件的子进程入口点执行的测试文件路径。仅在测试使用进程隔离运行时存在。如果测试定义在由入口文件导入的模块中,它可能与 file 不同。
测试文件的路径;如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号;如果测试是通过 REPL 运行的,则为 undefined
message:string
日志消息。
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
外层测试的 testId ;顶层测试为 undefined
testId:number
生成该日志消息的测试实例的数字标识符。

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

Attributes
data:Object
column:number | undefined
定义测试所在的列号;如果测试是通过 REPL 运行的,则为 undefined
details:Object
额外的执行元数据。
duration_ms:number
测试持续的毫秒数。
测试的类型,用于表示这是否为一个测试套件。
attempt:number | undefined
测试运行的尝试次数,仅在使用 --test-rerun-failures 标志时存在。
passed_on_attempt:number | undefined
测试通过时所处的尝试次数,仅在使用 --test-rerun-failures 标志时存在。
entryFile:string | undefined
作为触发此事件的子进程入口点而执行的测试文件路径。仅在测试通过进程隔离运行时存在。当测试定义在由入口文件导入的模块中时,它可能与 file 不同。
测试文件的路径;如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号;如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
包含该测试的父测试的 testId ;对于顶层测试为 undefined 。可让自定义报告器在同一嵌套层级的并发同级测试交错执行时跟踪其血缘关系。
tags:string[]
在测试及其祖先套件上声明的、展平后的小写标签,按声明顺序排列。未加标签的测试为空。参见 [Test tags][]。
testId:number
此测试实例的数值标识符,在测试文件的进程内唯一。对于同一测试实例,在所有事件中保持一致,便于自定义报告器进行可靠关联。
testNumber:number
测试的序号。
当调用 context.todo 时存在
当调用 context.skip 时存在

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

Attributes
data:Object
column:number | undefined
定义测试所在的列号,或者 如果测试是通过 REPL 运行的,则为 undefined
entryFile:string | undefined
作为发出此事件的子进程入口点执行的测试文件路径。 仅在测试以进程隔离方式运行时存在。当测试定义在由入口文件导入的模块中时,它可能与 file 不同。
测试文件的路径, 如果测试是通过 REPL 运行的,则为 undefined
定义测试所在的行号,或者 如果测试是通过 REPL 运行的,则为 undefined
nesting:number
测试的嵌套级别。
count:number
已运行的子测试数量。

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

Attributes
data:Object
column:number | undefined
定义测试的列号,或者 如果测试是通过 REPL 运行的,则为 undefined
entryFile:string | undefined
作为触发此事件的子进程入口点而执行的测试文件路径。 仅在测试以进程隔离方式运行时存在。当测试定义在由入口文件导入的模块中时,它可能与 file 不同。
测试文件的路径, 如果测试是通过 REPL 运行的,则为 undefined
定义测试的行号,或者 如果测试是通过 REPL 运行的,则为 undefined
name:string
测试名称。
nesting:number
测试的嵌套层级。
parentId:number | undefined
包含该测试的父测试的 testId ,或者 顶层测试为 undefined 。即使同一嵌套层级的并发兄弟测试交错执行,也能帮助自定义报告器跟踪谱系。
tags:string[]
在测试及其祖先套件上声明的、展平并转换为小写的标签,按声明顺序排列。未加标签的测试为空。 参见 测试标签
testId:number
此测试实例的数字标识符,在测试文件的进程内唯一。 对同一测试实例的所有事件保持一致,使自定义报告器能够可靠关联。

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

Attributes
data:Object
entryFile:string | undefined
作为触发此事件的子进程入口点而执行的测试文件路径。 仅在测试以进程隔离方式运行时存在。
file:string
测试文件的路径。
message:string
写入 stderr 的消息。

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

Attributes
data:Object
entryFile:string | undefined
作为触发此事件的子进程入口点执行的测试文件路径。仅当测试以进程隔离方式运行时存在。
file:string
测试文件的路径。
message:string
写入 stdout 的消息。

当正在运行的测试向 stdout 写入内容时触发。 仅当传入 --test 标志时才会触发此事件。 不保证此事件按照测试定义的顺序触发。

Attributes
data:Object
counts:Object
一个包含各种测试结果计数的对象。
cancelled:number
已取消测试的总数。
failed:number
失败测试的总数。
passed:number
通过测试的总数。
skipped:number
跳过测试的总数。
suites:number
运行的套件总数。
tests:number
运行的测试总数,不包括套件。
todo:number
TODO 测试的总数。
topLevel:number
顶层测试和套件的总数。
duration_ms:number
测试运行持续时间(毫秒)。
生成摘要的测试文件路径。如果摘要对应多个文件,则此值为 undefined
success:boolean
指示测试运行是否被视为成功。如果发生任何错误条件,例如测试失败或未达到覆盖率阈值,此值将设置为 false

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

当监视模式下没有更多测试排队等待执行时发出。

当监视模式下由于文件更改而重新启动一个或多个测试时发出。

M

getTestContext

History
getTestContext(): void

返回与当前正在执行的测试或套件关联的 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)内部调用时,此函数返回与该钩子关联的测试或套件的上下文。

测试埋点与 OpenTelemetry

History

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

测试运行器发布事件到 'node.test' 跟踪通道。订阅者 可以使用 TracingChannel API 来绑定上下文或执行自定义跟踪。

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

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

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

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

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

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

通过将 AsyncLocalStorage 实例绑定,测试跟踪通道可用于在测试执行期间传播上下文。这允许上下文在测试函数以及测试中的所有异步操作中自动可用。

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

History

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

M

context.before

History
context.before(fn?, options?): void
Attributes
钩子函数。此函数的第一个参数是 TestContext 对象。如果钩子使用回调函数,则回调函数会作为第二个参数传入。 默认值: 空操作函数。
options:Object
钩子的配置选项。支持以下属性:
允许中止正在进行的钩子。
timeout:number
钩子失败前等待的毫秒数。如果未指定,子测试将从其父测试继承此值。 默认值: Infinity

此函数注册一个钩子,该钩子会在当前测试的任何子测试之前运行。

M

context.beforeEach

History
context.beforeEach(fn?, options?): void
Attributes
钩子函数。此函数的第一个参数是 TestContext 对象。如果钩子使用回调,则回调函数作为第二个参数传递。 默认值: 一个空操作函数。
options:Object
钩子的配置选项。支持以下属性:
允许中止进行中的钩子。
timeout:number
钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。 默认值: Infinity

此函数注册一个钩子,该钩子会在当前测试的每个子测试之前运行。

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

context.after

History
context.after(fn?, options?): void
Attributes
钩子函数。此函数的第一个参数是 TestContext 对象。如果钩子使用回调,则回调函数作为第二个参数传递。 默认值: 一个空操作函数。
options:Object
钩子的配置选项。支持以下属性:
允许中止进行中的钩子。
timeout:number
钩子将在多少毫秒后失败。如果未指定,子测试将从其父级继承此值。 默认值: Infinity

此函数注册一个在当前测试完成后运行的钩子。

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

context.afterEach

History
context.afterEach(fn?, options?): void
Attributes
钩子函数。此函数的第一个参数是 TestContext 对象。如果钩子使用回调,则回调函数会作为第二个参数传入。 默认值: 无操作函数。
options:Object
钩子的配置选项。支持以下属性:
允许中止正在进行的钩子。
timeout:number
钩子失败前等待的毫秒数。如果未指定,子测试将从其父测试继承此值。 默认值: Infinity

此函数注册一个钩子,该钩子会在当前测试的每个子测试之后运行。

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

context.assert

History

一个包含绑定到 context 的断言方法的对象。node:assert 模块中的顶层函数在此处暴露,用于创建测试计划。

test('test', (t) => {
  t.plan(1);
  t.assert.strictEqual(true, true);
});
M

context.assert.fileSnapshot

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

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

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

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

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

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

context.assert.snapshot(value, options?): void
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)],
  });
});
M

context.diagnostic

History
context.diagnostic(message): void
Attributes
message:string
要报告的消息。

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

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

context.log

History
context.log(message, data?): void
Attributes
message:string
要报告的消息。
data:any
可选的结构化负载,附加到消息上。测试运行器会原样传递它。在使用进程隔离运行测试时,该值必须与 HTML 结构化克隆算法 兼容。

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

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

context.filePath

History

创建当前测试的测试文件的绝对路径。如果测试文件导入生成测试的其他模块,则导入的测试将返回根测试文件的路径。

P

context.fullName

History

测试及其每个祖先的名称,由 > 分隔。

P

context.name

History

测试名称。

P

context.passed

History
  • 类型:boolean 在测试执行之前为 false,例如在 beforeEach 钩子中。

指示测试是否成功。

P

context.error

History

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

P

context.attempt

History

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

P

context.tags

History

稳定性:1.0 - 早期开发

展平后的由小写标签组成的数组,按声明顺序排列,包括从祖先套件继承的所有标签。如果测试没有标签,则为空。请参阅测试标签

P

context.workerId

History

运行当前测试文件的工作线程的唯一标识符。此值源自 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 分配独立的资源
});
Attributes
count:number
预期运行的断言和子测试的数量。
options:Object
计划的附加选项。
计划的等待时间:

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

注意:为了确保断言被追踪,必须使用 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 超时,它仅在测试函数执行完成后才开始倒计时。

M

context.runOnly

History
context.runOnly(shouldRunOnlyTests): void
Attributes
shouldRunOnlyTests:boolean
是否只运行带有 only 选项的测试。

如果 shouldRunOnlyTests 为真值,测试上下文将只运行设置了 only 选项的测试。否则,将运行所有测试。如果 Node.js 启动时未使用 --test-only 命令行选项,则此函数不执行任何操作。

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

context.signal

History

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

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

context.skip

History
context.skip(message?): void
Attributes
message:string
可选的跳过消息。

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

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

context.todo

History
context.todo(message?): void
Attributes
message:string
可选的 TODO 消息。

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

test('顶级测试', (t) => {
  // 此测试被标记为 `TODO`
  t.todo('这是一个待办事项');
});
context.test(name?, options?, fn?): void
Attributes
name:string
子测试的名称,在报告测试结果时显示。 默认值: fnname 属性,若 fn 没有名称,则为 '<anonymous>'
options:Object
子测试的配置选项。支持以下属性:
concurrency:number | boolean | null
如果提供数字,则会异步运行这么多个测试(它们仍由单线程事件循环管理)。 如果为 true ,则会并行运行所有子测试。 如果为 false ,则一次只运行一个测试。 如果未指定,子测试将从其父级继承此值。 默认值: null
only:boolean
如果为真值,并且测试上下文配置为运行 only 测试,则此测试将运行。否则,测试将被跳过。 默认值: false
允许中止进行中的测试。
如果为真值,则测试被跳过。如果提供字符串,则该字符串会作为跳过该测试的原因显示在测试结果中。 默认值: false
tags:string[]
与子测试关联的字符串标签数组。 与 --experimental-test-tag-filter 一起使用,用于筛选要运行的测试。标签通过并集从父测试或套件继承。参见 测试标签默认值: []
如果为真值,则测试标记为 TODO 。如果提供字符串,则该字符串会作为测试为何为 TODO 的原因显示在测试结果中。 默认值: false
timeout:number
测试在多少毫秒后失败。 如果未指定,子测试将从其父级继承此值。 默认值: Infinity
plan:number
测试中预期运行的断言和子测试数量。 如果测试中运行的断言数量与计划中指定的数量不匹配,测试将失败。 默认值: undefined
被测试的函数。此函数的第一个参数是一个 TestContext 对象。如果测试使用回调,则回调函数作为第二个参数传递。 默认值: 一个空操作函数。

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

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

context.waitFor

History
context.waitFor(condition, options?): void
Attributes
一个谓词函数,将定期调用,直到成功完成或配置的轮询超时。成功完成的定义是未抛出异常或未拒绝。此函数不接受任何参数,并且可以返回任意值。
options:Object
轮询操作的可选配置对象。支持以下属性:
interval:number
condition 调用失败后、重试前等待的毫秒数。 默认值: 50
timeout:number
轮询超时时间,以毫秒为单位。如果在此时间过去之前 condition 尚未成功完成,则会发生错误。 默认值: 1000

此方法会轮询 condition 函数,直到其成功完成或操作超时。

类:SuiteContext

History

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

P

context.filePath

History

创建当前套件的测试文件的绝对路径。如果测试文件导入了生成套件的其他模块,导入的套件将返回根测试文件的路径。

P

context.fullName

History

套件及其每个祖先的名称,由 > 分隔。

P

context.name

History

套件的名称。

P

context.signal

History

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

P

context.passed

History

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

P

context.attempt

History

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

M

context.diagnostic

History
context.diagnostic(message): void
Attributes
message:string
要输出的诊断消息。

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

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

context.log

History
context.log(message, data?): void
Attributes
message:string
要报告的消息。
data:any
可选的结构化载荷,附加到消息上。测试运行器会原样传递它。

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

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