jest-circus 事件驱动测试运行器:从事件模型到自定义环境扩展的完整指南

发布时间:2026/9/19 20:04:34

jest-circus 事件驱动测试运行器:从事件模型到自定义环境扩展的完整指南 jest-circus 事件驱动测试运行器从事件模型到自定义环境扩展的完整指南【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest导读jest-circus是 Jest 的下一代测试运行器next-gen test runner自 Jest 27 起已成为 Jest 的默认测试运行器。它采用基于 Flux 架构的事件分发模型event-driven/flux-based将测试声明、Hook 执行、测试运行与结果汇总解耦为一系列可订阅的事件开发者可以在自定义测试环境中通过handleTestEvent订阅并观察测试运行的全过程。读完本文你将掌握jest-circus的安装配置、事件体系同步事件与异步事件、状态对象结构并能基于自定义测试环境编写事件处理器实现自定义报告、超时统计、失败诊断等扩展能力。一、jest-circus 是什么Flux 风格的事件驱动架构从官方定义看Circus 是一个基于 Flux 架构的 Jest 测试运行器其设计目标是快速fast、可维护maintainable、易于扩展simple to extend。所谓 Flux 风格体现在 state.ts 的核心实现中单一状态源State整个测试运行期间的所有状态当前 describe 块、正在运行的测试、未处理的错误、随机种子等被集中存放在globalThis[STATE_SYM]指向的 State 对象中通过getState()/setState()读写resetState()重置事件分发dispatch所有行为变更都以事件形式派发dispatch(event)依次调用所有已注册的事件处理器await handler(event, getState())同步事件则走dispatchSync处理器链Handlers默认注册了eventHandler核心状态机与formatNodeAssertErrors断言错误格式化两个处理器用户可以通过addEventHandler/removeEventHandler增删自定义处理器。// packages/jest-circus/src/state.ts节选 const handlers: ArrayCircus.EventHandler globalThis[EVENT_HANDLERS] || [ eventHandler, formatNodeAssertErrors, ]; export const dispatch async (event: Circus.AsyncEvent): Promisevoid { for (const handler of handlers) { await handler(event, getState()); } }; export const dispatchSync (event: Circus.SyncEvent): void { for (const handler of handlers) { handler(event, getState()); } }; export const addEventHandler (handler: Circus.EventHandler): void { handlers.push(handler); };也就是说谁在什么时候执行什么完全由事件流驱动describe/test/beforeEach等 API 在声明时只负责派发同步事件见 index.ts 中_addTest、_addHook对dispatchSync的调用真正决定测试如何运行的逻辑则集中在 run.ts 的run()中——它派发run_start、遍历根 describe 块、按顺序执行 hook 与测试、最终派发run_finish并汇总RunResult。二、事件订阅在自定义环境中使用 handleTestEventCircus 允许开发者通过自定义测试环境custom environment上的可选事件处理器绑定到这些事件上。所谓自定义环境即配置项testEnvironment指向的自定义类它继承自jest-environment-node或jest-environment-jsdom提供的基础环境。在 jest-environment-node 的基类中handleTestEvent被定义为可覆写的空实现Circus 在运行时会调用环境实例上的该方法并把事件对象与状态对象一起传入。官方 README 给出的标准用法如下import type {Event, State} from jest-circus; import {TestEnvironment as NodeEnvironment} from jest-environment-node; class MyCustomEnvironment extends NodeEnvironment { //... async handleTestEvent(event: Event, state: State) { if (event.name test_start) { // ... } } }代码中Event与State两个类型从jest-circus导出对应 index.ts 中的export type Event Circus.Event; export type State Circus.State;其完整定义位于仓库的 packages/jest-types/src/Circus.ts。事件与状态数据的只读约定阅读事件模型时需要注意两个官方明确声明的约定不支持修改事件或状态数据在handleTestEvent中修改event或state属于未支持行为可能导致意外行为甚至在未来某个版本中无警告地失效不构成破坏性变更新增事件、新增事件字段或状态字段不会被视为破坏性变更可能在任何 minor 版本中出现。这意味着插件代码应只依赖文档化的既有事件不要假设事件集合固定不变。同步事件例外不是所有事件都会等待Circus 默认会暂停执行直到handleTestEvent返回的 Promise 被 resolve即处理器可以异步地观察每个事件。但以下同步事件不遵循该规则出于向后兼容原因与process.on(unhandledRejection, callback)的签名限制有关start_describe_definitionfinish_describe_definitionadd_hookadd_testerror这些事件在 Circus.ts 中被归类为SyncEvent通过dispatchSync派发处理器无法通过返回 Promise 来延迟这些事件的处理流程。对大多数使用场景而言这通常不会造成问题——它们大多是声明阶段的同步操作注册 describe、hook、test而不是真正耗时的运行阶段事件。三、完整事件清单同步事件与异步事件要编写可靠的事件处理器需要掌握完整的事件类型。以下依据 packages/jest-types/src/Circus.ts 整理。3.1 同步事件SyncEvent同步事件携带的数据极少且处理器不能通过 Promise 延迟执行事件名携带字段触发时机start_describe_definitionblockName、mode、asyncError开始注册一个describe块finish_describe_definitionblockName、mode结束注册一个describe块add_hookhookType、fn、timeout、asyncError注册beforeAll/beforeEach/afterEach/afterAlladd_testtestName、fn、mode、concurrent、timeout、failing、asyncError注册一个测试用例errorerror、promise?发生在测试/Hook 之外的未处理错误error_handledpromise之前未处理的 Promise 被后续处理其中mode类型为void | skip | only | todo对应test.skip、test.only、test.todo等修饰符concurrent标识该测试是否通过test.concurrent声明failing标识是否通过test.failing声明期望失败的测试。3.2 异步事件AsyncEvent异步事件覆盖了测试运行的完整生命周期处理器返回的 Promise 会被等待事件名关键负载语义setuptestNamePattern?、runtimeGlobals、parentProcess第一个派发的事件适合初始化各类设置同时注入全局错误处理器include_test_location_in_result—在结果中包含测试位置run_start/run_finish—整个文件测试运行开始 / 结束run_describe_start/run_describe_finishdescribeBlock某个 describe 块开始 / 结束运行hook_starthookHook 开始执行hook_success/hook_failurehook、describeBlock?、test?、error?Hook 执行成功 / 失败test_starttest单个测试开始包括其所有 hook 与测试函数test_fn_starttest仅测试函数本身开始执行test_fn_success/test_fn_failuretest、error?测试函数成功 / 失败test_retrytest测试失败后即将重试describe_retrydescribeBlockdescribe 块整体重试describe.retrytest_startedtest测试实际开始未跳过test_skip/test_todotest测试被跳过 / 标记为 todotest_donetest测试及其全部 hook 均运行完毕状态最终确定concurrent_tests_start/concurrent_tests_endtests、describeBlock一组并发测试test.concurrent开始 / 结束teardown—一切结束、即将向上层返回结果前的收尾事件恢复全局错误处理器3.3 状态对象State的关键字段State在 Circus.ts 中定义处理器可以通过它读取当前运行上下文type State { currentDescribeBlock: DescribeBlock; // 当前正在注册的 describe 块 currentlyRunningTest?: TestEntry | null; // 当前正在运行的测试含 hook 执行期间 hasFocusedTests: boolean; // 是否存在 test.only hasStarted: boolean; // 是否已开始运行 parentProcess: Process | null; // 外层 process 对象 randomize?: boolean; // 是否随机执行randomize 配置 rootDescribeBlock: DescribeBlock; // 根 describe 块 seed: number; // 随机种子 testNamePattern?: RegExp | null; // -t 名称过滤模式 testTimeout: number; // 默认超时默认 5000ms maxConcurrency: number; // 并发测试最大并发数默认 5 unhandledErrors: ArrayException; // 未处理错误列表 // ...含 describe 重试选项、未处理拒绝错误映射等 };这些默认值testTimeout: 5000、maxConcurrency: 5可以在 state.ts 的createState()中直接看到。四、安装与配置4.1 安装注意自 Jest 27 起jest-circus已是 Jest 的默认测试运行器因此使用 Jest 时无需单独安装即可直接使用。如需在旧版本项目或独立场景下显式安装可通过 yarnyarn add --dev jest-circus或通过 npmnpm install --save-dev jest-circus4.2 配置 testRunner通过testRunner配置项指定使用jest-circus{ testRunner: jest-circus/runner }也可以使用 CLI 参数临时指定jest --testRunnerjest-circus/runner从源码看jest-circus/runner入口runner.ts实际导出的是 legacy-code-todo-rewrite/jestAdapter.ts 中的jestAdapter——它是 Jest 运行器与 Circus 之间的适配层负责初始化环境、注入beforeEach按配置执行resetModules/clearMocks/resetMocks/restoreMocks、加载setupFilesAfterEnv、加载测试文件、运行并转换结果最后把快照数据合并进TestResult见_addSnapshotData。五、事件流在源码中的具体实现5.1 声明阶段同步事件构建测试树当测试文件执行describe/it/beforeEach时index.ts 中的 API 只是把信息包装成同步事件派发出去// 以 add_test 为例 return dispatchSync({ asyncError, concurrent, failing: failing undefined ? false : failing, fn, mode, name: add_test, testName, timeout, });核心状态机 eventHandler.ts 的add_test分支会把测试挂到当前 describe 块下并处理各种非法声明测试嵌套在测试内部Cannot nest a describe inside a test/Tests cannot be nested测试在运行开始后才声明Tests must be defined synchronously在没有测试的 describe 块中使用 hookInvalid: beforeEach() may not be used in a describe block containing no tests.describe回调返回 Promise 或返回值Returning a Promise from describe is not supported。finish_describe_definition分支还会完成mode的向下传递describe.skip内的测试全部继承 skip与hasFocusedTests的标记存在test.only时置为 true。5.2 运行阶段run() 的执行顺序run.ts 的_runTestsForDescribeBlockOnce实现了标准的执行顺序派发run_describe_start执行该 describe 块的所有beforeAll若未 skip若开启了随机执行randomize配置用种子化的伪随机数生成器打乱子节点顺序shuffleArray按序处理子节点嵌套 describe 块递归运行普通测试执行beforeEach → 测试函数 → afterEach并发测试test.concurrent被regroupConcurrentChildren聚合成一个整体通过p-limit以maxConcurrency默认 5限流并发执行若配置了测试重试testRetries失败的测试在全部测试结束后再重跑执行该 describe 块的所有afterAll派发run_describe_finish。值得注意的实现细节afterAll失败不会改变单个测试的 pass/fail 状态run.ts 中test_done在 afterEach 之后立即派发afterAll的失败被计入全局unhandledErrorsbeforeAll失败则会被摊派到该 describe 块下的每个测试addErrorToEachTestUnderDescribe。5.3 describe.retry描述块级重试在较新版本中Circus 还支持describe.retry块级重试。run.ts中的_runTestsForDescribeBlock会检查state.describeRetryOptions中是否存在该 describe 块的重试配置numRetries、logErrorsBeforeRetry、waitBeforeRetry若存在则循环执行整个块直到无错误、重试次数耗尽或出现外部状态expect外部断言状态、进程级错误等不可重试情况并派发describe_retry事件。这也印证了事件清单中的describe_retry与test_retry两类重试事件的分工。六、实践编写一个可用的自定义环境事件处理器综合以上知识一个完整的自定义环境示例可以这样组织假设项目配置testEnvironment指向该文件或通过environmentOptions传入import type {Event, State} from jest-circus; import {TestEnvironment as NodeEnvironment} from jest-environment-node; type SlowTestInfo {fullName: string; duration: number}; class ObservingEnvironment extends NodeEnvironment { private slowTests: ArraySlowTestInfo []; async handleTestEvent(event: Event, state: State) { switch (event.name) { case run_start: { // 运行开始可以在此做初始化 this.slowTests []; break; } case test_start: { // 单测开始含 hook 阶段注意事件与状态数据不可修改 console.log([start] ${event.test.name}); break; } case test_fn_success: case test_fn_failure: { // 测试函数执行结果 break; } case test_done: { const {name, duration, errors} event.test; if (typeof duration number duration 1000) { this.slowTests.push({fullName: name, duration}); } // errors 非空即为失败 break; } case run_finish: { // 运行结束汇总慢测试 console.table(this.slowTests); break; } case teardown: { // 收尾恢复全局错误处理器等工作已由内部完成 break; } default: break; } } } export default ObservingEnvironment;使用约束提醒不要修改event/state它们被设计为只读视图除start_describe_definition、finish_describe_definition、add_hook、add_test、error五个同步事件外处理器返回的 Promise 都会被等待因此可以在处理器内执行异步工作但需注意不要影响测试本身的时序语义如需自定义全局事件处理器链而非仅环境内观察可以使用addEventHandler/removeEventHandler见 state.ts或通过EVENT_HANDLERSSymbol 注入处理器列表事件与状态类型定义集中在 packages/jest-types/src/Circus.ts这是编写处理器时的API 参考手册。七、进一步探索事件状态机核心实现packages/jest-circus/src/eventHandler.ts运行调度顺序、并发、重试packages/jest-circus/src/run.ts测试/Hook API 与事件派发packages/jest-circus/src/index.ts状态管理getState/setState/dispatchpackages/jest-circus/src/state.tsJest 适配层runner 入口packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts事件与状态类型定义packages/jest-types/src/Circus.ts事件处理相关的测试用例packages/jest-circus/src/tests/eventHandler.test.ts、packages/jest-circus/src/tests/run.test.ts、packages/jest-circus/src/tests/hooks.test.ts总结而言理解jest-circus的关键在于把握事件驱动 集中状态两条主线测试的生命周期被拆解为run_start → run_describe_start → hook_start → test_start → test_fn_start → … → test_done → run_finish → teardown的事件序列任何关注点报告、统计、诊断、自定义行为都可以通过handleTestEvent挂接到这条事件流上这正是它simple to extend的架构基础。【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/19 20:04:34

Manus Agent实现原理:多Agent协作、工具调用与断点恢复

简介:Manus 实现原理解析.pdf 是一份深入剖析 Manus 智能代理系统工作机制的技术文档,面向人工智能学习者、智能体开发者以及希望借助自动化完成复杂任务的工程技术人员,帮助读者理解该系统的核心运行逻辑。文件以规划机制、代码使用策略和交…

2026/9/19 20:04:34

API 超时反复重试?TaoToken + Roo Code 这样验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 20:04:34

Claude.ai 加 Kling MCP 生成视频,模型通道走 TaoToken 行不行?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 4:14:59

AllData国产化适配实战:鲲鹏海光麒麟欧拉与OceanBase

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 4:14:59

C/C++ static关键字深度解析:从底层原理到工程实践

先说结论:static修饰局部变量改变的是生命周期和存储位置,static修饰全局变量改变的是链接属性,static修饰函数同样改变链接属性,而C里static用在类成员上还有另一层含义。这个知识点几乎每个人都背过,可真到项目里&am…

2026/9/20 4:14:59

高通Adreno开源驱动Turnip详解:从下载安装到kalama显示IC开发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 0:04:49

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/20 0:04:49

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/20 0:04:49

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/20 0:04:49

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码