coverage
type CoverageOptions = {
enabled?: boolean;
provider?: 'istanbul' | 'v8';
include?: string[];
changed?: boolean | string;
exclude?: string[];
reporters?: CoverageReporter[];
reportsDirectory?: string;
reportOnFailure?: boolean;
clean?: boolean;
allowExternal?: boolean;
thresholds?: CoverageThresholds;
};
type CoverageReporter = string | [string, Record<string, unknown>] | ReportBase;
收集测试覆盖率信息并生成覆盖率报告。
$ npx rstest --coverage
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
选项
enabled
- 类型:
boolean
- 默认值:
false
- CLI:
--coverage, --coverage=false, --no-coverage
启用或禁用测试覆盖率收集。
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
},
});
provider
- 类型:
'istanbul' | 'v8'
- 默认值:
'istanbul'
- CLI:
--coverage.provider <provider>
选择覆盖率收集方式。Rstest 同时支持 istanbul 和 v8。如果你更关注基于 SWC 插桩的转换性能和稳定的 Istanbul 语义,可以选择 istanbul;如果开启 coverage 后主要受内存压力影响,可以选择 v8,它通常有更低的运行时内存占用,因为不会为每个执行模块注入覆盖率计数器。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
provider: 'v8',
},
});
Istanbul provider
Istanbul 是一个广泛使用的 JavaScript 代码覆盖率分析工具,它通过插桩的方式来收集代码覆盖率信息。
要启用 istanbul 覆盖率,需要先安装 @rstest/coverage-istanbul。
npm add @rstest/coverage-istanbul -D
yarn add @rstest/coverage-istanbul -D
pnpm add @rstest/coverage-istanbul -D
bun add @rstest/coverage-istanbul -D
deno add npm:@rstest/coverage-istanbul -D
@rstest/coverage-istanbul 由 swc-plugin-coverage-instrument 提供支持。Rstest 会在 transform 阶段通过 SWC 完成插桩,因此该 provider 具有较好的 transform 性能。但插桩后的代码会在执行时保留用于收集覆盖率的计数器,因此运行时内存占用可能更高。
序列化函数和其他 realm
Istanbul 会向插桩后的文件注入 cov_* 覆盖率计数器调用。这些计数器的作用域属于 Rstest 执行的 transformed module。如果来自插桩文件的代码被序列化后在其他位置执行,新的 scope 或 realm 可能没有对应的 cov_* helper,测试就可能以 ReferenceError: cov_... is not defined 失败。
当函数通过 fn.toString() 序列化,或者通过 page.evaluate、Worker、node:vm、eval、new Function 等 API 执行时,可能触发这个问题。要避免该错误,可以通过 coverage.exclude 排除对应源文件;在非 Browser Mode 测试中切换到 coverage.provider: 'v8';或者避免序列化经过 Istanbul 插桩的函数。v8 provider 在 Browser Mode 中不可用,因此浏览器测试应使用 coverage.exclude,或避免序列化经过插桩的函数。
如果整个文件主要用于生成、序列化,或向其他 scope 或 realm 注入代码,优先使用 coverage.exclude。Istanbul ignore 注释更适合在一个仍需要收集覆盖率的文件中,跳过很小的独立片段。
在配置中排除整个文件:
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
provider: 'istanbul',
exclude: ['**/src/injected-script.ts', '**/src/**/*.{worker,evaluate}.ts'],
},
});
也可以在临时执行时通过 CLI 传入相同的规则:
npx rstest run --coverage --coverage.exclude "**/src/injected-script.ts" --coverage.exclude "**/src/**/*.{worker,evaluate}.ts"
如果整个文件都不应被 Istanbul 插桩,可以在文件顶部添加 /* istanbul ignore file */:
src/injected-script.ts
/* istanbul ignore file */
export function createInjectedScript() {
return `globalThis.__APP_READY__ = true;`;
}
如果只有一个会被序列化的函数需要跳过,可以把 /* istanbul ignore next */ 放在它前面:
/* istanbul ignore next */
function browserEvaluateFn() {
return window.location.href;
}
await page.evaluate(browserEvaluateFn);
对于很小的环境判断分支,也可以使用 /* istanbul ignore if */ 和 /* istanbul ignore else */。如果被忽略的片段里仍然出现了注入的 cov_* 调用,请改用 coverage.exclude 排除对应源文件。
V8 provider
v8 provider 基于 Node.js inspector precise coverage 收集数据,并重映射为 Istanbul 格式。相比 Istanbul 插桩,V8 coverage 通常有更低的运行时内存占用,因为执行模块中不需要额外的覆盖率计数器。它更适合大型非 Browser Mode 测试套件中 coverage 内存压力明显的场景。
要启用 V8 覆盖率,需要先安装 @rstest/coverage-v8。
npm add @rstest/coverage-v8 -D
yarn add @rstest/coverage-v8 -D
pnpm add @rstest/coverage-v8 -D
bun add @rstest/coverage-v8 -D
deno add npm:@rstest/coverage-v8 -D
由于底层覆盖率数据来自 Node.js / V8 本身,重映射后的 branch 覆盖率和未覆盖行信息在不同 Node.js 或 V8 版本之间可能存在轻微差异。
v8 provider 仅适用于非 Browser Mode 测试执行,包括 node、jsdom 和 happy-dom 测试环境。Browser Mode 不使用 inspector coverage,因此浏览器 coverage 应使用默认的 istanbul provider。
include
- 类型:
string[]
- 默认值:
undefined
- CLI:
--coverage.include <pattern>
对匹配 glob 规则的文件进行测试覆盖率收集。重复传入 CLI 参数可以指定多个模式。
默认情况下,Rstest 会收集已测试文件的覆盖率。如果你希望在覆盖率报告中包含未测试的文件,可以使用 include 选项指定要包含的文件或模式。
需要注意的是,这里应使用标准 glob 语法。匹配单个扩展名时,请写 src/**/*.ts,不要写 src/**/*.{ts}。底层 glob 库会把单元素花括号按字面量处理,因此 src/**/*.{ts} 不会匹配 .ts 文件。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
include: ['src/**/*.{js,jsx,ts,tsx}'],
},
});
changed
- 类型:
boolean | string
- 默认值:
undefined
- CLI:
--coverage.changed, --coverage.changed=<commit>
只收集当前 Git 仓库中变更文件的覆盖率。你也可以传入 commit 或 branch,收集从该 ref 以来变更文件的覆盖率。
这个选项既可以写在配置里,也可以通过 CLI 使用:
- 当你希望在项目里长期保持一个默认行为时,使用
rstest.config.ts 中的 coverage.changed,例如在 CI 中。
- 当你只想临时跑一次时,使用
--coverage.changed。
coverage.changed 只控制覆盖率报告范围,不会改变要运行的测试。
也就是说,下面两个命令的效果并不一样:
npx rstest run --changed
npx rstest run --coverage.changed
--changed 会改变测试选择范围,只运行与变更文件相关的测试。
--coverage.changed 会保持正常的测试选择范围,只把覆盖率报告限制在变更的源码文件上。
与 --changed 的关系
- 当使用
--changed 且没有配置 coverage.changed 时,覆盖率报告会继承 --changed 收集到的变更文件。
- 如果
--changed 命中了 forceRerunTriggers,Rstest 会回退为运行完整测试套件。此时覆盖率默认也会回到完整范围,除非显式启用了 coverage.changed。
- 如果你想用
--changed 缩小测试执行范围,但仍然保留完整覆盖率报告,可以在配置中将 coverage.changed 设为 false。
常见用法如下:
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
changed: 'origin/main',
},
});
上面的配置不会改变测试执行范围,但会让覆盖率报告只包含相对 origin/main 有变更的文件。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
changed: 'HEAD~1',
},
});
npx rstest run --changed --coverage
npx rstest run --coverage.changed
npx rstest run --coverage.changed=HEAD~1
npx rstest run --coverage.changed=origin/main
npx rstest run --changed --coverage 会运行变更相关测试,并且默认把覆盖率也限制到同一批变更文件。
npx rstest run --coverage.changed=HEAD~1 会执行正常测试集,但只报告相对 HEAD~1 以来变更文件的覆盖率。
npx rstest run --coverage.changed=origin/main 适合在功能分支上使用:它仍然会执行正常测试集,但覆盖率报告只包含相对 origin/main 变更过的文件。
exclude
- 类型:
string[]
- CLI:
--coverage.exclude <pattern>
- 默认值:
[
'**/node_modules/**',
'**/__tests__/**',
'**/__mocks__/**',
'**/*.d.ts',
'**/*.{test,spec}.[jt]s',
'**/*.{test,spec}.[cm][jt]s',
'**/*.{test,spec}.[jt]sx',
'**/*.{test,spec}.[cm][jt]sx',
];
匹配 glob 规则的文件将从测试覆盖率收集中排除。自定义配置将与默认值合并。
Rstest 默认不会排除名为 test 的目录。这样可以避免误排除路径中包含 test 片段的 package 或 workspace 源文件,例如 packages/test/src/index.ts。如果你希望从覆盖率中排除这些目录,可以显式添加 test/** 或 **/test/** 等规则。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
exclude: ['**/node_modules/**', '**/dist/**'],
},
});
reporters
- 类型:
CoverageReporter[]
- 默认值:
['text', 'html', 'clover', 'json']
- CLI:
--coverage.reporters <reporter>
用于覆盖率收集的报告器。Rstest 使用标准 Istanbul reporter API,而不是 Rstest 专属的 coverage reporter API。
每个 reporter 可以是字符串(reporter 名称)、包含 reporter 名称及其选项的元组,也可以是带有 execute(context) 方法且兼容 Istanbul 的 reporter 对象。
CLI 仅支持传入报告器名称。如需指定多个报告器,可以重复使用该参数:
npx rstest run --coverage.reporters text --coverage.reporters=json
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reporters: [
'html',
['text', { skipFull: true }],
['json', { file: 'coverage-final.json' }],
],
},
});
自定义 coverage reporter
自定义 coverage reporter 应遵循 Istanbul reporter 约定。reporter 包或文件会由 Rstest 的 coverage provider 加载(在可能的情况下会通过 istanbul-reports 创建),使用 coverage.reporters 中传入的选项创建实例,然后用 Istanbul report context 调用。
例如,一个 ESM reporter 可以导出带有 execute 方法的 class。context 参数对应 istanbul-lib-report 中的 Istanbul Context 类型:
custom-coverage-reporter.mjs
export default class CustomCoverageReporter {
constructor(options = {}) {
this.options = options;
}
/** @param {import('istanbul-lib-report').Context} context */
execute(context) {
// Use the standard Istanbul report context.
const summary = context.getTree('flat').getRoot().getCoverageSummary();
console.log('Coverage summary:', summary.toJSON());
}
}
然后在 rstest.config.ts 中通过包名或文件路径引用该 reporter:
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reporters: [
'text',
['./custom-coverage-reporter.mjs', { outputFile: 'summary.json' }],
],
},
});
如果在配置中直接创建 reporter,也可以传入兼容 Istanbul 的 reporter 对象:
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reporters: [
{
execute(context) {
const summary = context
.getTree('flat')
.getRoot()
.getCoverageSummary();
console.log(summary.toJSON());
},
},
],
},
});
reportsDirectory
- 类型:
string
- 默认值:
'./coverage'
- CLI:
--coverage.reportsDirectory <dir>
存储覆盖率报告的目录。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reportsDirectory: './coverage-reports',
},
});
reportOnFailure
- 类型:
boolean
- 默认值:
false
- CLI:
--coverage.reportOnFailure
在测试失败时是否生成覆盖率报告并检查阈值。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reportOnFailure: true,
},
});
allowExternal
- 类型:
boolean
- 默认值:
false
- CLI:
--coverage.allowExternal
是否收集项目根目录之外的源文件的覆盖率。这在 monorepo 中非常有用,例如测试文件导入了来自同级工作区包的模块。
默认情况下,Rstest 会从覆盖率报告中排除项目根目录之外的文件,这与 Jest 和 Vitest 的行为一致。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
allowExternal: true,
},
});
clean
- 类型:
boolean
- 默认值:
true
- CLI:
--coverage.clean、--coverage.clean=false
是否在运行测试之前清理覆盖率目录。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
clean: true,
},
});
thresholds
type CoverageThreshold = {
statements?: number;
functions?: number;
branches?: number;
lines?: number;
};
type CoverageThresholds = CoverageThreshold & {
/** 为匹配的文件指定覆盖率阈值 */
[glob: string]: CoverageThreshold;
};
设置最低代码覆盖率要求。你可以为语句、函数、分支和行覆盖率设置阈值。
当阈值设置为正数时,表示所需的最低百分比。当阈值设置为负数时,表示允许未覆盖的最大数量。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
statements: 80,
functions: 80,
branches: 80,
lines: -10,
},
},
});
当代码覆盖率低于指定阈值时,测试将失败并输出如下错误信息:
Error: Coverage for statements 75% does not meet global threshold 80%
Error: Coverage for functions 75% does not meet global threshold 80%
Error: Coverage for branches 75% does not meet global threshold 80%
Error: Uncovered lines 20 exceeds maximum global threshold allowed 10
glob 模式
当指定 glob 模式时,Rstest 将根据匹配的文件模式进行代码覆盖率检查。如果指定的文件路径不存在,则返回错误。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
// 为匹配的文件指定覆盖率阈值
'src/**': {
statements: 100,
},
'node/**/*.js': {
statements: 90,
},
// 指定所有文件的总阈值
statements: 80,
},
},
});
根据以上配置,rstest 将在以下情况下失败:
src/** 匹配到的文件的总语句覆盖率低于 100%。
node/**/*.js 匹配到的文件的总语句覆盖率低于 90%。
- 全局的语句覆盖率低于 80%。
单文件检查
Rstest 支持通过将 perFile 设置为 true 来为每个匹配文件分别检查阈值。
rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
'src/**': {
statements: 90,
perFile: true,
},
},
},
});