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
expectSaga (integration) — preferred; doesn't couple tests to effect ordering
testSaga (unit) — only when effect ordering is part of the contract
runSaga (no library) — lightweight; uses jest/vitest spies directly
- Manual
.next() — last resort; most brittle
Core Pattern
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('fetches user successfully', () => {
return expectSaga(fetchUserSaga, { payload: { userId: 1 } })
.provide([
[matchers.call.fn(api.fetchUser), { id: 1, name: 'Alice' }],
])
.put(fetchUserSuccess({ id: 1, name: 'Alice' }))
.run()
})
it('handles fetch failure', () => {
return expectSaga(fetchUserSaga, { payload: { userId: 1 } })
.provide([
[matchers.call.fn(api.fetchUser), throwError(new Error('500'))],
])
.put(fetchUserFailure('500'))
.run()
})
Assertion Methods
| Method |
Purpose |
.put(action) |
Dispatches this action |
.put.like({ action: { type } }) |
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)
.provide([
[matchers.call.fn(api.fetchUser), mockUser], // match by function
[call(api.fetchUser, 1), mockUser], // match by function + exact args
[matchers.select.selector(getToken), 'mock-token'], // mock selector
[matchers.call.fn(api.save), throwError(error)], // simulate error
])
Dynamic Providers
.provide({
call(effect, next) {
if (effect.fn === api.fetchUser) return mockUser
return next() // pass through
},
select({ selector }, next) {
if (selector === getToken) return 'mock-token'
return next()
},
})
Rules
- 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
- API Reference — Complete
expectSaga, testSaga, providers, matchers
- Anti-Patterns — Common testing mistakes to avoid
1---2name: redux-saga-testing3description: Write tests for Redux Sagas using redux-saga-test-plan, runSaga, and manual generator testing. Covers expectSaga (integration), testSaga (unit), providers, partial matchers, reducer integration, error simulation, and cancellation testing. Works with Jest and Vitest. Triggers on: test files for sagas, redux-saga-test-plan imports, mentions of "test saga", "saga test", "expectSaga", "testSaga", or "redux-saga-test-plan".4license: MIT5---67# Redux-Saga Testing Guide89**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.1011## Approach Priority12131. **`expectSaga` (integration)** — preferred; doesn't couple tests to effect ordering142. **`testSaga` (unit)** — only when effect ordering is part of the contract153. **`runSaga` (no library)** — lightweight; uses jest/vitest spies directly164. **Manual `.next()`** — last resort; most brittle1718## Core Pattern1920```javascript21import { expectSaga } from 'redux-saga-test-plan'22import * as matchers from 'redux-saga-test-plan/matchers'23import { throwError } from 'redux-saga-test-plan/providers'2425it('fetches user successfully', () => {26 return expectSaga(fetchUserSaga, { payload: { userId: 1 } })27 .provide([28 [matchers.call.fn(api.fetchUser), { id: 1, name: 'Alice' }],29 ])30 .put(fetchUserSuccess({ id: 1, name: 'Alice' }))31 .run()32})3334it('handles fetch failure', () => {35 return expectSaga(fetchUserSaga, { payload: { userId: 1 } })36 .provide([37 [matchers.call.fn(api.fetchUser), throwError(new Error('500'))],38 ])39 .put(fetchUserFailure('500'))40 .run()41})42```4344## Assertion Methods4546| Method | Purpose |47|--------|---------|48| `.put(action)` | Dispatches this action |49| `.put.like({ action: { type } })` | Partial action match |50| `.call(fn, ...args)` | Calls this function with exact args |51| `.call.fn(fn)` | Calls this function (any args) |52| `.fork(fn, ...args)` | Forks this function |53| `.select(selector)` | Uses this selector |54| `.take(pattern)` | Takes this pattern |55| `.dispatch(action)` | Simulate incoming action |56| `.not.put(action)` | Does NOT dispatch |57| `.returns(value)` | Saga returns this value |58| `.run()` | Execute (returns Promise) |59| `.run({ timeout })` | Execute with custom timeout |60| `.silentRun()` | Execute, suppress timeout warnings |6162## Provider Types6364### Static Providers (Preferred)6566```javascript67.provide([68 [matchers.call.fn(api.fetchUser), mockUser], // match by function69 [call(api.fetchUser, 1), mockUser], // match by function + exact args70 [matchers.select.selector(getToken), 'mock-token'], // mock selector71 [matchers.call.fn(api.save), throwError(error)], // simulate error72])73```7475### Dynamic Providers7677```javascript78.provide({79 call(effect, next) {80 if (effect.fn === api.fetchUser) return mockUser81 return next() // pass through82 },83 select({ selector }, next) {84 if (selector === getToken) return 'mock-token'85 return next()86 },87})88```8990## Rules91921. **Prefer `expectSaga`** over `testSaga` — integration tests don't break on refactors932. **Use `matchers.call.fn()`** for partial matching — don't couple to exact args unless necessary943. **Use `throwError()`** from providers — not `throw new Error()` in the provider954. **Test with reducer** using `.withReducer()` + `.hasFinalState()` to verify state965. **Dispatch actions** with `.dispatch()` to simulate user flows in tests976. **Return the promise** (Jest) or `await` it (Vitest) — don't forget async987. **Use `.not.put()`** to assert actions are NOT dispatched (negative tests)998. **Test cancellation** by dispatching cancel actions and asserting cleanup effects1009. **Use `.silentRun()`** when saga runs indefinitely (watchers) to suppress timeout warnings10110. **Don't test implementation** — test behavior (what actions are dispatched, what state results)102103## Anti-Patterns104105See [references/anti-patterns.md](references/anti-patterns.md) for BAD/GOOD examples of:106107- Step-by-step tests that break on reorder108- Missing providers (real API calls in tests)109- Testing effect order instead of behavior110- Forgetting async (Jest/Vitest)111- Inline mocking instead of providers112- Not testing error paths113- Not testing cancellation cleanup114115## References116117- [API Reference](references/api-reference.md) — Complete `expectSaga`, `testSaga`, providers, matchers118- [Anti-Patterns](references/anti-patterns.md) — Common testing mistakes to avoid