@remix-run/test 与 `remix test`:从 v0.1.0 到 v0.6.0 的测试框架演进全解

发布时间:2026/9/10 8:08:03
@remix-run/test 与 `remix test`:从 v0.1.0 到 v0.6.0 的测试框架演进全解 remix-run/test 与remix test从 v0.1.0 到 v0.6.0 的测试框架演进全解【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remixremix-run/test是 Remix 内置的 JavaScript/TypeScript 测试框架提供describe/it单元测试、Playwright 驱动的浏览器与 E2E 测试、统一代码覆盖率报告以及 watch 模式。本文以该包的 CHANGELOG 为主线结合 README 与 源码 实现系统梳理其从 v0.1.0 初始发布到 v0.6.0 的关键能力演进CLI 如何收敛为remix test、配置如何统一到remix.json#test、runRemixTest()程序化 API 如何使用以及 timeout/signal、--only聚焦、fake timers、覆盖率等能力的底层原理。读完你既能掌握当前版本的完整用法也能理解每项特性背后的设计取舍与实现细节。版本脉络总览remix-run/test的演进大致分为三个阶段版本阶段主题核心变化v0.1.0初始发布框架 API、TestContext、Playwright E2E、独立remix-testCLI、watch、配置与 setup 钩子v0.2.0能力补全glob.exclude、代码覆盖率、程序化runRemixTest()导出、Bun 运行时支持v0.3.0执行模型重构数组型 glob、advanceAsync、默认pool: forksfork 子进程v0.4.x加载器内化从tsx迁移到内部remix-run/node-tsx浏览器测试稳定化v0.5.0超时与取消{ timeout, signal }、字符串skip/todo原因v0.6.0CLI 收敛移除独立remix-test统一为remix testremix.json#test新增--only/--quiet当前包版本为 v0.6.0见 packages/test/package.json要求 Node.js ≥ 24.3.0playwright为可选 peer 依赖^1.60.0因此纯 server 测试或查看帮助时无需安装 Playwright。一、v0.6.0CLI 收敛与remix.json#test配置中心v0.6.0 是配置与调用方式的一次大重构核心目标是把测试运行统一收敛到 Remix 主 CLI 之下。1.1 移除独立remix-test可执行文件旧版通过独立remix-test命令运行测试新版要求改用remix test- remix-test --type server --concurrency 1 remix test --type server --concurrency 1同时remix-test.config.ts/remix-test.config.js的自动发现被移除。静态测试配置统一放入项目根目录remix.json的test字段由主 Remix CLI 负责 JSONC 解析、路径解析、校验与优先级合并见 CHANGELOG v0.6.0。一个完整的配置示例JSONC 支持注释与尾逗号{ $schema: ./node_modules/remix/schema/remix.json, test: { files: [**/*.test{,.browser,.e2e}.{ts,tsx}], browserFiles: [**/*.test.browser.{ts,tsx}], e2eFiles: [**/*.test.e2e.{ts,tsx}], exclude: [node_modules/**, dist/**], type: [server, browser, e2e], only: [/checkout/i], concurrency: 2, pool: forks, setup: ./test/setup.ts, watch: false, playwright: { echo: false, open: false, configFile: ./playwright.config.ts, projects: [chromium, firefox] }, reporter: spec, quiet: false, coverage: { enabled: true, dir: .coverage, include: [src/**], exclude: [src/**/*.test.ts], statements: 80, lines: 80, branches: 80, functions: 80 } } }配置语义要点对应 config.ts 的RemixTestConfig与resolveConfig每个字段都可选未设置时回落到 runner 默认值defaultValues见 config.ts默认glob.test为[**/*.test{,.e2e,.browser}.{ts,tsx}]glob.exclude默认[node_modules/**]type默认[server, browser, e2e]concurrency默认取os.availableParallelism()reporter在 CI 下默认files、本地默认spec相对路径与 glob 均相对配置文件所在目录解析显式 CLI 标志与位置参数优先可重复的标志会替换配置中的数组Playwright 与 coverage 的嵌套值按字段合并。1.2runRemixTest()结构化程序化 APIremix-run/test/cli导出的runRemixTest()只接受结构化调用选项不再接受argv数组也不再读取process.argv或处理 CLI 帮助见 cli.tsimport { runRemixTest } from remix-run/test/cli let exitCode await runRemixTest({ cwd: process.cwd(), type: [server], concurrency: 1, glob: { test: src/**/*.test.ts }, })程序化调用可以传入更丰富的值例如内联的 Playwright 配置对象或RegExp形式的测试名过滤器。runRemixTest()不会退出宿主进程而是返回退出码0成功、1失败由调用方决定如何终止同时它不负责加载配置文件——remix.json的加载由主 Remix CLI 完成再以解析后的选项调用 runner。这保证了打开中的 worker、浏览器或项目句柄不会让 CLI 悬挂不退出。1.3 新能力--only聚焦与--quiet静默v0.6.0 新增两条 CLI 标志--only pattern对应配置only按套件名或完整测试名匹配来聚焦测试无需修改源码添加.only修饰符。匹配套件名会聚焦整个套件匹配测试名则聚焦单条测试--quiet/-q对应配置quiet从 reporter 输出中省略被跳过的测试。同时修复了.only过滤的作用域问题聚焦的测试与套件现在作用于整个测试模块而不再局限于最近的describe块当describe.only与it.only同时存在时runner 执行聚焦套件 ∪ 聚焦测试的并集见 CHANGELOG v0.6.0。1.4NODE_ENV默认值remix test在未设置NODE_ENV时默认置为test使测试文件加载的应用模块可以可靠地选择测试专用资源如内存数据库显式设置的值会被保留。这在源码中的实现是process.env.NODE_ENV ?? test见 cli.ts发生在 runner 真正执行之前保证测试文件与 worker 进程都能看到该环境变量。1.5coverage.enabled: inheritremix-run/test/cli现在导出remixTestPools支持的pool取值同时coverage.enabled接受inherit表示不在本层决定是否开启覆盖率而是交给配置文件决定同时仍可细化其他覆盖率设置见 CHANGELOG v0.6.0。在 config.ts 中inherit与false一样不会在本层启用 coverage。二、v0.5.0timeout、AbortSignal 与跳过原因v0.5.0 为测试与生命周期钩子引入了{ timeout, signal }选项并支持字符串形式的skip/todo原因it(loads data, { timeout: 5_000 }, async (t) { let response await fetch(/api/data, { signal: t.signal }) assert.equal(response.status, 200) }) it(depends on external credentials, { skip: requires API credentials }, () {})实现上TestContext.signal是测试超时或用户提供信号中止时会被 abort 的信号见 context.ts。因此所有接受AbortSignal的异步工作如fetch、文件操作都能在超时瞬间及时取消避免悬挂。beforeEach等钩子同样可以传超时beforeEach( async () { await resetDatabase() }, { timeout: 1_000 }, )三、v0.4.x加载器内化与浏览器测试稳定化3.1 从tsx迁移到remix-run/node-tsxv0.4.0 将.ts/.tsx/.jsx模块加载从tsx包迁移到 Remix 内部维护的remix-run/node-tsx加载器。测试模块在执行前仍会经过转换包括需要 JS 输出的 JSX 与 TypeScript 语法只是加载器变为 Remix 自研并维护见 CHANGELOG v0.4.0。从 package.json 可以看到runner 依赖中还包含es-module-lexer、esbuild、get-tsconfig、magic-string、source-map-js与oxc-resolver这些共同支撑模块加载与源码转换链路。3.2 浏览器测试的稳定性修复v0.4.x 累积了一批针对浏览器/E2E 场景的修复忽略浏览器端被取消的脚本请求使 iframe 导航在 Windows 上也能干净收尾同时不掩盖真实的脚本加载失败v0.4.0 Patch裸包导入按浏览器与 ESM 条件解析避免命中clsx这类包的 CommonJS 入口v0.4.2见 CHANGELOG v0.4.2每个 Playwright project 内串行执行浏览器与 E2E 测试避免同时启动过多浏览器、减少 CI 上的时序抖动v0.4.2beforeAll/afterEach/afterAll钩子抛错时上报为失败用例保证 runner 非零退出v0.4.2浏览器测试服务器改用操作系统分配的端口避免并行运行耗尽固定端口窗口v0.4.2大型套件只要单个测试文件持续上报进度即可超过每文件超时v0.4.0 Patch。四、v0.3.0执行模型与发现能力的升级4.1advanceAsync(ms)支持异步依赖的假时钟v0.3.0 为t.useFakeTimers()新增advanceAsync(ms)。与同步的advance一样按时间顺序遍历待触发定时器但每次触发之间会让出微任务使 promise 延续以及它们新调度的定时器先落定再处理下一个触发。当假时钟驱动的回调内部 await 的工作本身依赖假时钟时必须使用它。实现见 fake-timers.ts每次next.fn()后执行await Promise.resolve()排空微任务队列。fake timers 会替换setTimeout、setInterval、clearTimeout、clearInterval与Date.now且测试结束后自动恢复fake-timers.ts。一个防抖场景示例it(debounces a callback, (t) { let timers t.useFakeTimers() let calls 0 let debounced debounce(() calls, 300) debounced() timers.advance(299) assert.equal(calls, 0) timers.advance(1) assert.equal(calls, 1) })FakeTimers接口提供advance(ms)、advanceAsync(ms)与restore()三个方法fake-timers.ts。4.2 数组型 glob 与位置参数glob.{test,browser,e2e,exclude}、project、type、coverage.{include,exclude}均接受数组对应的 CLI 标志--glob.test、--project、--type等可重复传入。remix test后的位置参数会收集进glob.testremix test src/**/*.test.ts tests/**/*.test.tsxtype的默认值由字符串server,browser,e2e改为数组[server, browser, e2e]。此外所有 reporter 的结束摘要现在都包含测试文件/套件的总数。4.3 默认pool: forks与延迟加载 Playwrightv0.3.0 将 server 与 E2E 测试文件默认改为在fork 子进程中运行pool: threads--pool threads保留旧的 worker-thread 行为worker 资源在结果上报后会被清理。从 runner.ts 可以看到pool threads走Workerworker_threads否则走forkchild_processremixTestPools [forks, threads]config.ts。forks提供更强的隔离threads启动开销更低。同时 Playwright 改为仅在真正运行浏览器或 E2E 测试时加载因此未安装 Playwright 也能查看帮助或运行纯 server 测试缺失时给出明确报错npm i -D playwright。五、v0.2.0覆盖率与程序化入口5.1glob.exclude与代码覆盖率v0.2.0 引入glob.exclude默认node_modules/**用于在测试发现阶段过滤路径同时加入代码覆盖率报告remix test --coverage或配置coverage: true以默认设置开启或通过coverage对象细化dir输出目录默认.coverage、include/exclude包含/排除的 glob 数组、statements/lines/branches/functions四类覆盖率百分比阈值。覆盖率收集采用V8 原生覆盖率 v8-to-istanbul 转换方案与decisions/004-v8-vs-istanbul-instrumentation.md中的决策一致server 测试通过设置NODE_V8_COVERAGE环境变量收集 V8 JSON 数据浏览器/E2E 测试通过 Playwright 的page.coverage.startJSCoverage仅 Chromium收集见 runner.ts 与 context.ts。最终generateCombinedCoverageReport()会把 server、browser、e2e 各阶段收集到的覆盖率 map合并过滤 include/exclude 后写出 text 与 LCOV 报告并校验阈值——任一阈值未达标都会导致整体非零退出coverage.ts。因此你可以用一套配置同时覆盖单元测试与端到端测试的代码路径。5.2runRemixTest()导出与 Bun 支持v0.2.0 首次从remix-run/test/cli导出runRemixTest()使其他工具可以在不退出宿主进程的情况下程序化运行测试remix-test可执行文件同时在包元数据中声明 Node.js ≥ 24.3.0。该版本还重构了测试发现逻辑以支持 BunBun 的fs.promises.glob会跟随符号链接且不通过exclude剪枝在 pnpm workspace 中会陷入node_modules符号链接环。重构后Bun 运行时改用其原生Glob类默认followSymlinks: false避免循环Node 运行时继续使用fs.promises.glob见 cli.ts并用原生动态import()加载.ts/.tsx文件。六、v0.1.0初始发布与框架 APIv0.1.0 奠定了remix-run/test的全部核心概念见 CHANGELOG v0.1.0describe/it测试结构配套before/after/beforeEach/afterEach钩子suite/test为别名见 index.ts 导出与 README每个测试的TestContexttt.mock.fn()、t.mock.method()、t.after()清理方法 mock 在测试结束后自动恢复context.ts通过t.serve()进行 Playwright E2E 测试——传入运行中的测试服务器返回指向其baseURL的Page服务器与页面在测试结束后自动关闭context.tsCLIremix-test覆盖全部配置选项的标志watch 模式--watch与配置文件支持remix-test.config.ts通过setup模块提供globalSetup/globalTeardown在整个测试运行前/后各调用一次。一个最基础的使用示例测试文件导入自remix/testimport * as assert from remix/assert import { describe, it } from remix/test describe(My Test Suite, () { it(tests a function, () { let result something() assert.equal(result, 42) }) })setup钩子的典型形态// ./test/setup.ts export async function globalSetup() { await db.migrate() } export async function globalTeardown() { await db.close() }七、三种测试形态与当前 CLI 全貌7.1 server / browser / e2e 三分默认 glob 与type字段共同决定测试如何被执行发现逻辑见 cli.ts形态默认 glob执行方式server**/*.test.{ts,tsx}非 browser/e2e 文件fork 子进程或 threads中跑describe/itbrowser**/*.test.browser.{ts,tsx}Playwright 浏览器环境每个套件在独立iframe中运行配套remix/ui/test的render()e2e**/*.test.e2e.{ts,tsx}Playwright 驱动真实浏览器通过t.serve()连接测试服务器E2E 示例使用remix-run/node-fetch-server/test的createTestServerimport * as assert from remix/assert import { createTestServer } from remix/node-fetch-server/test import { describe, it } from remix/test import { createRouter } from ./router.ts describe(checkout, () { it(adds an item to the cart, async (t) { let router createRouter() let server await createTestServer(router.fetch) let page await t.serve(server) await page.goto(/) await page.getByRole(button, { name: Add to Cart }).click() await page.getByRole(heading, { name: Shopping Cart }).waitFor() }) })Playwright 的浏览器、超时、视口等可执行设置放在playwright.config.ts中并在remix.json里通过test.playwright.configFile引用设置test.playwright.open: true或--browser.open可在测试结束后保持浏览器打开便于调试失败。7.2 当前 CLI 标志速查所有测试设置都可以作为 CLI 标志传入布尔设置提供否定形式--no-*FlagShort--concurrency n-c--coverage/--no-coverage--coverage.dir path、--coverage.include、--coverage.exclude、--coverage.statements、--coverage.lines、--coverage.branches、--coverage.functions--glob.test、--glob.browser、--glob.e2e、--glob.exclude--playwrightConfig path--only pattern--pool forks\|threads--project name-p--quiet/--no-quiet-q--reporter name-r--setup path-s--type name-t--watch/--no-watch-w另有全局--config标志选择其他 Remix 配置文件路径相对当前工作目录解析remix test --config ./config/remix.ci.json八、--only聚焦过滤的语义细节--only的匹配规则值得单独说明实现见 config.ts 的resolveOnlyPatterns完整测试名由嵌套describe名与测试名以连接如describe(Cart routes, () describe(loader, () it(loads cart items, ...)))的完整名为Cart routes loader loads cart items纯字符串模式不区分大小写按 JavaScript 正则匹配套件名与完整测试名斜杠包裹的模式保留其标志/pattern/区分大小写/pattern/i不区分程序化调用还可直接传RegExp值。remix test --only Cart routes remix test --only Checkout routes redirects anonymous users remix test --only /anonymous users$/非法正则会在解析阶段直接抛错并提示正确写法/pattern/flags。结语一次收敛一套配置三种执行形态回顾remix-run/test的完整演进可以清晰看到两条主线一是执行模型持续向稳定性与可观测性收敛——worker 从线程默认改为 fork 进程、Playwright 延迟加载、浏览器测试串行化与端口随机化、钩子失败正确上报二是接口层面向单一入口收敛——独立remix-test与remix-test.config.ts被移除配置统一进remix.json#test程序化调用统一为结构化参数的runRemixTest()CLI 统一为remix test。与此同时timeout/signal 取消、advanceAsync假时钟、--only聚焦与覆盖率阈值等能力不断补强使它在 server 单元测试、浏览器组件测试与 Playwright E2E 三种形态下都能提供一致的开发体验。对 Remix 应用开发者而言只需记住一条命令remix test与一份配置remix.json#test即可获得从单元到端到端的完整测试闭环。【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考