Metadata 允许你为测试、套件和文件结果附加可 JSON 序列化的数据。内置 Reporter 默认不会打印这些数据;它主要面向自定义 Reporter、programmatic API、CI 集成、仪表盘、归属关系映射,以及 mutation testing runner 等外部工具。
在测试选项对象中传入 meta,即可设置测试用例的初始 metadata。
import { expect, test } from '@rstest/core';
test(
'charges credit card',
{ meta: { owner: 'payments', feature: 'checkout' } },
() => {
expect(1 + 1).toBe(2);
},
);
Reporter hooks 和 programmatic API 可以从 TestResult.meta 读取这些声明的 metadata。你可以在测试声明时定义 metadata,也可以在测试运行过程中通过 task.meta 修改或添加字段:
test('records runtime data', ({ task }) => {
task.meta = {
durationBucket: 'fast',
};
});
当多个测试共享相同字段时,可以把 meta 传给 describe。
import { describe, expect, test } from '@rstest/core';
describe(
'checkout',
{ meta: { owner: 'payments', layer: 'integration' } },
() => {
test('creates an order', ({ task }) => {
// task.meta 是 { owner: 'payments', layer: 'integration' }
expect(task.meta.owner).toBe('payments');
});
test(
'uses coupon',
{ meta: { layer: 'e2e', caseId: 'PAY-456' } },
({ task }) => {
// task.meta 是 { owner: 'payments', layer: 'e2e', caseId: 'PAY-456' }
expect(task.meta.layer).toBe('e2e');
},
);
},
);
套件 metadata 会被子套件和测试继承。子级顶层字段会覆盖父级同名字段,因此单个测试只需要覆盖不同的字段。
生命周期 Hook 可以通过 ctx.meta 获取 metadata 对象。
import { afterAll, describe, test } from '@rstest/core';
afterAll((ctx) => {
ctx.meta.fileTag = 'ci-shard-1';
});
describe('checkout', { meta: { owner: 'payments' } }, () => {
afterAll((ctx) => {
ctx.meta.suiteDurationBucket = 'slow';
});
test('creates an order', () => {
// ...
});
});
文件级 Hook 会写入 TestFileResult.meta。describe 内部的 Hook 会写入对应套件结果的 meta,并通过 onTestSuiteResult 传给自定义 Reporter。
自定义 Reporter 可以从 case、suite 和 file 结果中读取 metadata。
import type { Reporter } from '@rstest/core';
const metadataReporter: Reporter = {
onTestCaseStart(test) {
// 经过套件继承和测试级覆盖后的初始 metadata。
console.log('case start', test.name, test.meta);
},
onTestCaseResult(result) {
// 测试执行和 hooks 运行后的最终 metadata。
console.log('case result', result.name, result.meta);
},
onTestSuiteResult(result) {
console.log('suite result', result.name, result.meta);
},
onTestFileResult(result) {
console.log('file result', result.testPath, result.meta);
},
};
如果需要测试运行前声明的 metadata,使用 onTestCaseStart。如果需要运行时产生的最终 metadata,使用 onTestCaseResult、onTestSuiteResult 或 onTestFileResult。
runRstest 的结果中包含与 Reporter 相同的结果 metadata。
import { runRstest } from '@rstest/core/api';
const result = await runRstest({
cwd: process.cwd(),
inlineConfig: {
include: ['src/**/*.test.ts'],
},
});
for (const file of result.files) {
console.log(file.testPath, file.meta);
for (const test of file.results) {
console.log(test.name, test.meta);
}
}
当外部工具掌控 Node.js 进程,并希望运行 Rstest 后直接消费结构化结果,而不是解析终端输出时,这种方式很有用。
Metadata 必须可 JSON 序列化。TaskMeta 和 TaskMetaValue 从 @rstest/core 导出:
type TaskMeta = Record<string, TaskMetaValue>;
type TaskMetaValue =
| string
| number
| boolean
| null
| TaskMetaValue[]
| { [key: string]: TaskMetaValue };
不要在 metadata 中存储函数、类实例、symbol、BigInt 或大型对象。建议使用小型字符串、数字、布尔值、数组和普通对象。
继承和隔离规则
Rstest 在收集和上报 metadata 时遵循以下规则:
- 套件 metadata 会被子套件和测试继承。
- 子级顶层字段会覆盖父级顶层字段。
- 继承的 metadata 会为每个子级复制,因此运行时修改不会泄漏到兄弟测试或兄弟套件。
- 嵌套对象和数组会在继承时复制。
- 在测试中修改
task.meta 会影响该测试最终的 TestResult.meta。
- 在文件级 Hook 中修改
ctx.meta 会影响 TestFileResult.meta;在套件 Hook 中修改它会影响该套件结果的 meta。
Skipped 和 todo 测试
跳过和 todo 测试仍然会保留声明的 metadata。
import { test } from '@rstest/core';
test.skip(
'blocked by upstream',
{ meta: { owner: 'payments', reason: 'upstream' } },
() => {
// 这个函数体不会运行。
},
);
test.todo('add refund coverage', {
meta: { owner: 'payments', tracking: 'PAY-789' },
});
因为这些测试不会执行,所以无法进行运行时修改,但 Reporter 仍然可以从 onTestCaseStart 和 onTestCaseResult 读取它们声明的 metadata。
常见使用场景包括:
- 归属字段,例如
owner、team 或 feature。
- 测试管理系统链接,例如
caseId 或 tracking。
- CI 仪表盘字段,例如 shard、environment 或 risk level。
- 自定义 Reporter 输出,例如 JSON、Markdown 或内部仪表盘。
- 需要把测试结果映射到源码分析的外部工具,例如 mutation testing runner。
最佳实践
- 保持值小且可 JSON 序列化。
- 优先使用稳定的字段名,例如
owner、feature、caseId 和 tracking。
- 将共享字段放在
describe 上,只在单个测试上覆盖例外。
- 只有当 metadata 依赖运行时发现的信息时,才在运行时写入。
- 使用自定义 Reporter 或 programmatic API 消费 metadata;内置 Reporter 更适合人类可读的测试输出。
相关 API