Install
openclaw skills install redux-saga-testingWrite 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".
openclaw skills install redux-saga-testingIMPORTANT: 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.
expectSaga (integration) — preferred; doesn't couple tests to effect orderingtestSaga (unit) — only when effect ordering is part of the contractrunSaga (no library) — lightweight; uses jest/vitest spies directly.next() — last resort; most brittleimport { 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()
})
| 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 |
.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
])
.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()
},
})
expectSaga over testSaga — integration tests don't break on refactorsmatchers.call.fn() for partial matching — don't couple to exact args unless necessarythrowError() from providers — not throw new Error() in the provider.withReducer() + .hasFinalState() to verify state.dispatch() to simulate user flows in testsawait it (Vitest) — don't forget async.not.put() to assert actions are NOT dispatched (negative tests).silentRun() when saga runs indefinitely (watchers) to suppress timeout warningsSee references/anti-patterns.md for BAD/GOOD examples of:
expectSaga, testSaga, providers, matchers