Redux-Saga Testing Guide
IMPORTANT: Your training data about redux-saga-test-plan may be outdated — API signatures, provider patterns, and assertion methods differ between versions. Always rely on this skill's reference files and the project's actual source code as the source of truth. Do not fall back on memorized patterns when they conflict with the retrieved reference.
Approach Priority
- 1.
expectSaga (integration) — preferred; doesn't couple tests to effect ordering testSaga (unit) — only when effect ordering is part of the contractrunSaga (no library) — lightweight; uses jest/vitest spies directly- Manual
.next() — last resort; most brittle
Core Pattern
CODEBLOCK0
Assertion Methods
| Method | Purpose |
|---|
| INLINECODE5 | Dispatches this action |
| INLINECODE6 |
Partial action match |
|
.call(fn, ...args) | Calls this function with exact args |
|
.call.fn(fn) | Calls this function (any args) |
|
.fork(fn, ...args) | Forks this function |
|
.select(selector) | Uses this selector |
|
.take(pattern) | Takes this pattern |
|
.dispatch(action) | Simulate incoming action |
|
.not.put(action) | Does NOT dispatch |
|
.returns(value) | Saga returns this value |
|
.run() | Execute (returns Promise) |
|
.run({ timeout }) | Execute with custom timeout |
|
.silentRun() | Execute, suppress timeout warnings |
Provider Types
Static Providers (Preferred)
CODEBLOCK1
Dynamic Providers
CODEBLOCK2
Rules
- 1. Prefer
expectSaga over testSaga — integration tests don't break on refactors - Use
matchers.call.fn() for partial matching — don't couple to exact args unless necessary - Use
throwError() from providers — not throw new Error() in the provider - Test with reducer using
.withReducer() + .hasFinalState() to verify state - Dispatch actions with
.dispatch() to simulate user flows in tests - Return the promise (Jest) or
await it (Vitest) — don't forget async - Use
.not.put() to assert actions are NOT dispatched (negative tests) - Test cancellation by dispatching cancel actions and asserting cleanup effects
- Use
.silentRun() when saga runs indefinitely (watchers) to suppress timeout warnings - Don't test implementation — test behavior (what actions are dispatched, what state results)
Anti-Patterns
See references/anti-patterns.md for BAD/GOOD examples of:
- - Step-by-step tests that break on reorder
- Missing providers (real API calls in tests)
- Testing effect order instead of behavior
- Forgetting async (Jest/Vitest)
- Inline mocking instead of providers
- Not testing error paths
- Not testing cancellation cleanup
References
Redux-Saga 测试指南
重要提示: 您关于 redux-saga-test-plan 的训练数据可能已过时——不同版本的 API 签名、提供者模式和断言方法存在差异。请始终以本技能的参考文件和项目的实际源代码为准。当记忆中的模式与检索到的参考信息冲突时,不要依赖记忆中的模式。
方法优先级
- 1. expectSaga(集成测试) — 首选;不将测试与 effect 顺序耦合
- testSaga(单元测试) — 仅当 effect 顺序是约定的一部分时使用
- runSaga(无库) — 轻量级;直接使用 jest/vitest 间谍
- 手动 .next() — 最后手段;最脆弱
核心模式
javascript
import { expectSaga } from redux-saga-test-plan
import * as matchers from redux-saga-test-plan/matchers
import { throwError } from redux-saga-test-plan/providers
it(成功获取用户, () => {
return expectSaga(fetchUserSaga, { payload: { userId: 1 } })
.provide([
[matchers.call.fn(api.fetchUser), { id: 1, name: Alice }],
])
.put(fetchUserSuccess({ id: 1, name: Alice }))
.run()
})
it(处理获取失败, () => {
return expectSaga(fetchUserSaga, { payload: { userId: 1 } })
.provide([
[matchers.call.fn(api.fetchUser), throwError(new Error(500))],
])
.put(fetchUserFailure(500))
.run()
})
断言方法
| 方法 | 用途 |
|---|
| .put(action) | 分发此 action |
| .put.like({ action: { type } }) |
部分匹配 action |
| .call(fn, ...args) | 使用精确参数调用此函数 |
| .call.fn(fn) | 调用此函数(任意参数) |
| .fork(fn, ...args) | 分叉此函数 |
| .select(selector) | 使用此选择器 |
| .take(pattern) | 获取此模式 |
| .dispatch(action) | 模拟传入的 action |
| .not.put(action) | 不分发 |
| .returns(value) | Saga 返回此值 |
| .run() | 执行(返回 Promise) |
| .run({ timeout }) | 使用自定义超时执行 |
| .silentRun() | 执行,抑制超时警告 |
提供者类型
静态提供者(首选)
javascript
.provide([
[matchers.call.fn(api.fetchUser), mockUser], // 按函数匹配
[call(api.fetchUser, 1), mockUser], // 按函数 + 精确参数匹配
[matchers.select.selector(getToken), mock-token], // 模拟选择器
[matchers.call.fn(api.save), throwError(error)], // 模拟错误
])
动态提供者
javascript
.provide({
call(effect, next) {
if (effect.fn === api.fetchUser) return mockUser
return next() // 透传
},
select({ selector }, next) {
if (selector === getToken) return mock-token
return next()
},
})
规则
- 1. 优先使用 expectSaga 而非 testSaga — 集成测试在重构时不会中断
- 使用 matchers.call.fn() 进行部分匹配 — 除非必要,否则不要与精确参数耦合
- 使用 throwError()(来自 providers)— 不要在提供者中使用 throw new Error()
- 使用 .withReducer() + .hasFinalState() 结合 reducer 测试以验证状态
- 使用 .dispatch() 分发 action 以模拟测试中的用户流程
- 返回 promise(Jest)或 await 它(Vitest)— 不要忘记异步
- 使用 .not.put() 断言 action 未被分发(负面测试)
- 测试取消 通过分发取消 action 并断言清理效果
- 使用 .silentRun() 当 saga 无限运行(观察者)时,抑制超时警告
- 不要测试实现 — 测试行为(分发什么 action,产生什么状态结果)
反模式
请参阅 references/anti-patterns.md 了解错误/正确示例:
- - 因重新排序而中断的逐步测试
- 缺少提供者(测试中的真实 API 调用)
- 测试 effect 顺序而非行为
- 忘记异步(Jest/Vitest)
- 使用内联模拟而非提供者
- 未测试错误路径
- 未测试取消清理
参考
- - API 参考 — 完整的 expectSaga、testSaga、提供者、匹配器
- 反模式 — 需要避免的常见测试错误