首页/新闻资讯/正文详情

使用 RxJS TestScheduler 为 redux-observable 编写确定性测试:Epic 单元测试完整指南

发布时间:2026/9/27 9:22:49 来源:云帆数科 栏目:资讯中心
使用 RxJS TestScheduler 为 redux-observable 编写确定性测试:Epic 单元测试完整指南
前端【免费下载链接】redux-observableRxJS middleware for action side effects in Redux using Epics项目地址https://gitcode.com/gh_mirrors/re/redux-observable点击查看免费下载本文以 redux-observable 官方文档 docs/recipes/WritingTests.md 为骨架系统讲解如何用 RxJS 的 TestScheduler 与 marble 图弹珠图为 Epic 编写确定、快速且不依赖真实时间的单元测试。读完本文你将掌握 Epic 的本质纯 RxJS 函数、依赖注入的测试价值以及testScheduler.run(callback)的完整用法并能直接在自己的项目中落地可复制的测试代码。为什么 Epic 天生容易测试它们只是使用 RxJS 的函数在深入测试技巧之前先理解一个核心事实Epic 本质上就是一个函数。查看仓库中的类型定义 src/epic.ts可以清楚地看到这一点export declare interface Epic Input unknown, Output extends Input Input, State void, Dependencies any { ( action$: ObservableInput, state$: StateObservableState, dependencies: Dependencies ): ObservableOutput; }一个 Epic 接收三个参数action$action 流、state$状态流和dependencies注入的依赖然后返回一个输出 action 的Observable。它除了遵循「输出{ type: string }对象」这一与 Redux 的约定外与 Redux 本身没有任何直接耦合。这意味着我们可以像调用普通函数一样调用 Epic自行传入 mock 的action$、state$以及任何依赖而无需真正初始化一个 Redux store。这正是整个测试策略的基石测试 Epic 就是测试一条 RxJS 管道。下面是一个非常简单的、我们将为其编写测试的 Epicconst fetchUserEpic (action$, state$, { getJSON }) action$.pipe( ofType(FETCH_USER), mergeMap((action) getJSON(https://api.github.com/users/${action.id}).pipe( map((response) ({ type: FETCH_USER_FULFILLED, response })), ), ), );注意其中的ofType操作符它在仓库中由 src/operators.ts 实现在非生产环境下如果以空参数或null/undefined调用会发出警告核心逻辑则是基于filter对单个 action type 做精确匹配或多个 type 时做循环匹配同时利用 TypeScript 的ExtractInput, ActionType做类型收窄。它的仓库测试 test/operators-spec.ts 验证了按单类型过滤、按多类型过滤以及警告行为。准备工作RxJS TestScheduler 与 marble 测试RxJS 自带了TestScheduler它通过虚拟化时间来编写确定性测试——时间在测试中是虚拟的你无需等待真实时间流逝因此测试不仅确定而且极快。在 RxJS v7 中新增了testScheduler.run(callback)辅助方法它在旧的 TestScheduler 行为之上提供了多项便利。在继续之前建议你熟悉testScheduler.run(callback)与 marble 图弹珠图的用法marble 语法中的-表示一帧虚拟时间a、b等字母代表具体的值hot(...)表示热流测试开始前就已在发射cold(...)表示冷流订阅后才开始发射expectObservable(output$).toBe(marbles, values)则断言输出流的时间线与取值。需要提醒学习和编写 marble 测试可能有一定难度。请记住这些是RxJS 的概念而非 redux-observable 的——web 上关于测试 RxJS 代码的其他文章同样对你测试 Epic 有帮助。核心是掌握 RxJS 的时间虚拟化能力再把它套到 Epic 上。同时要留意文档中的另一条建议上面的示例利用了我们内置的极简依赖注入能力将依赖作为第三个参数传入 Epic。很多测试框架提供了更好的 mock 设施例如 Jest 的 manual mocks 功能。选择最适合你项目的方案即可——测试工具不必拘泥于本文示例。用 marble 图测试 Epic完整示例现在我们使用testScheduler.run(callback)配合 marble 图来测试上面的fetchUserEpicimport { TestScheduler } from rxjs/testing; const testScheduler new TestScheduler((actual, expected) { // somehow assert the two objects are equal // e.g. with chai expect(actual).deep.equal(expected) }); testScheduler.run(({ hot, cold, expectObservable }) { const action$ hot(-a, { a: { type: FETCH_USER, id: 123 }, }); const state$ null; const dependencies { getJSON: (url) cold(--a, { a: { url }, }), }; const output$ fetchUserEpic(action$, state$, dependencies); expectObservable(output$).toBe(---a, { a: { type: FETCH_USER_FULFILLED, response: { url: https://api.github.com/users/123, }, }, }); });让我们逐步拆解这段测试的每一处细节action$ hot(-a, ...)用热流模拟真实 store 的 action 流。-a表示第 1 帧是空的、第 2 帧发射 action{ type: FETCH_USER, id: 123 }。state$ null因为fetchUserEpic根本没用到状态直接传null即可这正是「Epic 只是函数」带来的自由度。dependencies.getJSON用cold(--a, { a: { url } })mock 的getJSON返回一个冷流模拟真实的异步网络请求——在虚拟时间中「延迟 2 帧后」返回结果。由于它返回的是{ url }而不是真正的 HTTP 响应我们实际上同时 mock 掉了网络层。时间线的验证action$在第 2 帧发射-a中的agetJSON冷流延迟 2 帧--a因此最终输出在第 4 帧出现对应toBe(---a, ...)中的三个-后跟一个a。整个异步时序完全由 marble 图精确刻画测试是 100% 确定的并且运行耗时几乎为零。断言的实现new TestScheduler((actual, expected) { ... })中的回调用于比较实际输出与期望输出你可以用 chai 的expect(actual).deep.equal(expected)也可以用 Vitest/Jest 的expect(actual).toEqual(expected)。在 RxJS v7 的run()模式下该回调主要服务于expectObservable的自动断言。依赖注入让测试更省力的前置设计上面的测试之所以如此干净离不开依赖注入。如果fetchUserEpic内部直接import { ajax } from rxjs/ajax并使用ajax.getJSON那么测试时就得去 mockwindow.XMLHttpRequest之类的底层设施——这不仅工作量大而且意味着你不再只是测试自己的 Epic而是在顺带测试 RxJS 是否正确使用 XMLHttpRequest这不应是测试的目标。redux-observable 为此提供了官方方案通过createEpicMiddleware的dependencies配置项注入依赖。查看 src/createEpicMiddleware.ts 的源码interface OptionsD any { dependencies?: D; }中间件创建时接收dependencies选项随后在内部调用每个 Epic 时把它作为第三个参数传入见 src/createEpicMiddleware.ts 中的epic(action$, state$, options.dependencies!)。也就是说你提供的任何东西都会被作为第三个参数传给所有 Epic排在 store 之后。配置方式如下完整说明见 docs/recipes/InjectingDependenciesIntoEpics.mdimport { createEpicMiddleware, combineEpics } from redux-observable; import { ajax } from rxjs/ajax; import rootEpic from ./somewhere; const epicMiddleware createEpicMiddleware({ dependencies: { getJSON: ajax.getJSON }, }); epicMiddleware.run(rootEpic);于是 Epic 不再自己 import 网络库而是从第三个参数中解构使用// Notice the third argument is our injected dependencies! const fetchUserEpic (action$, state$, { getJSON }) action$.pipe( ofType(FETCH_USER), mergeMap(({ payload }) getJSON(/api/users/${payload}).pipe( map(response ({ type: FETCH_USER_FULFILLED, payload: response, })), ), );对应的测试因此变得极为简单——直接调用 Epic传入 mock 依赖import { of } from rxjs; import { fetchUserEpic } from ./somewhere/fetchUserEpic; const mockResponse { name: Bilbo Baggins }; const action$ of({ type: FETCH_USERS_REQUESTED }); const state$ null; // not needed for this epic const dependencies { getJSON: (url) of(mockResponse), }; // Adapt this example to your test framework and specific use cases const result$ fetchUserEpic(action$, state$, dependencies).pipe( toArray(), // buffers output until your Epic naturally completes() ); result$.subscribe((actions) { assertDeepEqual(actions, [ { type: FETCH_USER_FULFILLED, payload: mockResponse, }, ]); });这两种风格可以互相补充不需要精确时序时用of(...)toArray()缓冲输出的写法更直白需要验证异步时序、并发、竞态等行为时TestScheduler 的 marble 图是更强大的工具。这也是文档中推荐先掌握testScheduler.run(callback)的原因——它能覆盖最复杂的一类测试场景。深入原理被 mock 的state$与真实的 Epic 世界有读者可能会问测试中把state$传null真实运行时的state$又是什么理解这一点有助于你判断何时需要认真 mock 状态。查看 src/StateObservable.tsexport class StateObservableS extends ObservableS { value: S; // ... constructor(input$: ObservableS, initialState: S) { // 订阅者会先收到 this.value当前状态快照 // 之后每次 input$ 有新值且与旧值不同才更新 value 并通知订阅者 } }真实环境中中间件会以 Redux store 的getState()为初值构造StateObservable并在每个 action 派发后更新它见 src/createEpicMiddleware.ts 中stateSubject$.next(store.getState())的逻辑且保证 reducer 先于 Epic 收到 action从而避免状态过期。因此测试时如果 Epic 不读取state$传null完全没问题如本文第一个示例如果 Epic 依赖state$.value或订阅state$你可以传入一个BehaviorSubject或StateObservable配合of(...)构造的输入流甚至直接构造一个真实StateObservable实例——仓库自身的 test/combineEpics-spec.ts 就是如此做的new StateObservable(new Subject(), { I: am, a: store })然后传入组合后的 Epic。另外combineEpics的源码与测试也揭示了「Epic 只是函数」在组合层面的含义src/combineEpics.ts 将多个 Epic 用merge合并为一个新的函数并给合并后的函数命名如combineEpics(epic1, epic2)任何一个子 Epic 没有返回流时会抛出 TypeError。这意味着你可以为combineEpics后的根 Epic 编写与单个 Epic 完全同构的测试——传入任意 mock 的action$/state$/依赖订阅其输出并断言。仓库自带的测试现成的学习范本redux-observable 仓库自身就提供了与本主题完全一致的测试实践可以直接作为参考范本test/operators-spec.ts用Subject模拟action$验证ofType按单个 type、多个 type 过滤的输入输出以及空参数、空值调用的警告行为——展示了「用普通 Subject 模拟 action 流」这一比 TestScheduler 更轻量的测试思路test/combineEpics-spec.ts直接构造action$Subject与state$StateObservable调用组合后的 Epic 并订阅输出进行断言还验证了「未返回流的 Epic 会抛错」和「合并函数的命名规则」——这些都是函数行为层面的纯测试不涉及任何 Redux storetest/createEpicMiddleware-spec.ts针对中间件本体的测试覆盖dependencies注入、run()时机等集成行为适合在需要验证「Epic 与 store 的接线」时参考。进阶建议为常见模式建立你自己的抽象你会发现自己经常编写几乎相同的测试Epics 也常常如此。文档给出的实用建议是考虑围绕最常见模式创建自己的抽象以减少样板代码。例如可以封装一个createEpicTestHarness辅助函数把「构造 TestScheduler → 用 hot 流生成action$→ 用 cold 流生成 mock 依赖 → 调用 Epic → 断言输出」这几步收敛成声明式的 APIfunction testEpic(epic, { actionMarble, actionValues, dependencyMarbles, expectedMarble, expectedValues }) { const testScheduler new TestScheduler((actual, expected) { expect(actual).toEqual(expected); }); testScheduler.run(({ hot, cold, expectObservable }) { const action$ hot(actionMarble, actionValues); const dependencies Object.fromEntries( Object.entries(dependencyMarbles).map(([name, { marble, values }]) [ name, (url) cold(marble, { url, ...values }), ]), ); expectObservable(epic(action$, null, dependencies)).toBe(expectedMarble, expectedValues); }); }这类抽象能让多个 Epic 的测试保持一致的阅读结构也更容易在团队中推广。同时请记住这些技巧的核心是 RxJS 的测试能力而不是 redux-observable 专有的——把 TestScheduler、marble 语法、依赖注入这套方法论用好无论 Epic 规模如何增长测试都能保持确定、快速、可维护。赞分享前端【免费下载链接】redux-observableRxJS middleware for action side effects in Redux using Epics项目地址https://gitcode.com/gh_mirrors/re/redux-observable点击查看免费下载相关推荐EmojiOne Color彩色字体让数字沟通充满色彩魅力的开源解决方案EmojiOne Color彩色字体让数字沟通充满色彩魅力的开源解决方案 在数字时代表情符号已经成为我们日常沟通中不可或缺的元素。然而系统默认的黑白表情往设计系统RxJS v4 测试模块Rx.Testing完整指南用虚拟时间调度器编写确定性单元测试RxJS v4 测试模块Rx.Testing完整指南用虚拟时间调度器编写确定性单元测试 RxJSThe Reactive Extensions for后端什么是Library OS用LiteBox在30分钟里理解操作系统新范式什么是Library OS用LiteBox在30分钟里理解操作系统新范式 Library OS库操作系统 是一种把操作系统压缩进程序的新范式。开源项目上一篇diffusers-cd_bedroom256_l2卧室图像秒级生成模型下一篇MetaCodable实战构建企业级API响应模型的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

国内做性视频网站哪家好?3招解决没人访问难题
国内做性视频网站哪家好?3招解决没人访问难题

国内做性视频网站哪家好?3招解决没人访问难题 网站做好了没人访问,这行里太常见了。很多老板找外包,盯着【国内做性视频网站哪家好】看半天,结果上线后流量为零,钱打水漂。别怪平台不给量,是你选错了方向。正规建站讲究合规与性能,而非违规擦边。… · 2026/9/27 9:22:31

Apache Pulsar SQL 部署与 Presto Pulsar Connector 配置实战指南
Apache Pulsar SQL 部署与 Presto Pulsar Connector 配置实战指南

消息队列后端流处理 【免费下载链接】pulsar Apache Pulsar - distributed pub-sub messaging system 项目地址: https://gitcode.com/gh_mirrors/pulsar28/pulsar 点击查看 免费下载 本文以 Apache Pulsar 2.2.1 版本官方文档 sql-deployment-configurations 为核… · 2026/9/27 9:22:25

低空经济 8000 亿赛道起飞,无人机商飞平台的分账结算基建如何跟上?
低空经济 8000 亿赛道起飞,无人机商飞平台的分账结算基建如何跟上?

一、引言:业务高速增长,资金基建容易滞后低空经济政策持续落地,空域管理改革持续推进,无人机不再局限于娱乐航拍,大量商业化应用场景逐步跑通:农林植保服务、电力与河道航测巡检、工程地形测绘、商业活动航… · 2026/9/27 9:22:25

2026 考研真题复习
2026 考研真题复习

2026 section1 2026 Section I Use of English-CSDN博客 2026 Section I Use of English 单词,短语和 句子-CSDN博客 2026 Section I Use of English阅读理解做题方法-CSDN博客 text1 ​2026-Part A-Text 1-word-part2-CSDN博客 2026-Part A-Text 1-单词和难句… · 2026/9/27 9:59:08

340万下载、64万日活:Muse 是真爆发,还是 Meta 的流量魔术?
340万下载、64万日活:Muse 是真爆发,还是 Meta 的流量魔术?

上线十天登顶双榜,单日日活跳涨 27%,安装量却有 230 万到 430 万三种说法。Meta 的个人 AI Agent 想接管的,不只是聊天框——但这份热度里,有多少来自产品,有多少来自分发?写在前面:三分钟看懂这… · 2026/9/27 9:59:08

网站遭攻击后源码下载指南:3步止损避坑全解析
网站遭攻击后源码下载指南:3步止损避坑全解析

网站遭攻击后源码下载指南:3步止损避坑全解析 找建站公司最怕什么?不是界面丑,也不是功能少,而是怕被坑高价买一堆垃圾代码,最后网站遭攻击时才发现自己手里连个能看的源码都没有。很多老板花了几万块,结果只拿到一堆混淆过的加密文件,想自己维护或者… · 2026/9/27 9:58:50

美工是做什么的新手入门
美工是做什么的新手入门

别被割韭菜,美工工作本质速查手册 找建站公司怕被坑高价?别急,这份速查手册直接拆解真相。 很多老板觉得美工就是“画图的”,其实这是最大的误区。 不懂美工的技术边界,你就只能被动接受“一口价”。 威胁场景:当“美工”变成安全漏洞的入口… · 2026/9/27 9:58:43

wp-calypso 数据表格组件 Table 详解:语义化 HTML、无障碍支持与 compact 紧凑模式实践
wp-calypso 数据表格组件 Table 详解:语义化 HTML、无障碍支持与 compact 紧凑模式实践

前端CMS 【免费下载链接】wp-calypso The JavaScript and API powered WordPress.com 项目地址: https://gitcode.com/gh_mirrors/wp/wp-calypso 点击查看 免费下载 Table 是 wp-calypso 中用于展示表格型数据的 React 组件族,由 Table、TableRow、Tabl… · 2026/9/27 9:58:19

用 PHP SDK 调用 Claude Message Batches API:异步批量处理与结果收集实战指南
用 PHP SDK 调用 Claude Message Batches API:异步批量处理与结果收集实战指南

人工智能大模型AI 应用移动开发交互助手 【免费下载链接】rikkahub RikkaHub is an Android APP that supports for multiple LLM providers. 项目地址: https://gitcode.com/gh_mirrors/ri/rikkahub 点击查看 免费下载 导读 本文以 .agents/skills/claude-api/ph… · 2026/9/27 9:58:19

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码