redux-saga-tester
Full redux environment testing helper for redux-saga.
redux-saga is a great library that provides an easy way to test your sagas step-by-step, but it's tightly coupled to the saga implementation. Try a non-breaking reorder of the internal yield
s, and the tests will fail.
This tester library provides a full redux environment to run your sagas in, taking a black-box approach to testing. You can dispatch actions, observe the state of the store at any time, retrieve a history of actions and listen for specific actions to occur.
Getting Started
Installation
$ npm install --save-dev redux-saga-tester
Basic Example
Suppose we have a saga that waits for a START action, performs some async (or sync) actions (eg. fetching data from an API), and dispatches a SUCCESS
action upon completion. Here's how we would test it:
import ourSaga from './saga';
describe('ourSaga test', () => {
let sagaTester = null;
beforeEach(() => {
sagaTester = new SagaTester({ initialState });
sagaTester.start(ourSaga);
});
it('should retrieve data from the server and send a SUCCESS action', async () => {
sagaTester.dispatch(Actions.actions.start());
const successAction = await sagaTester.waitFor(Actions.types.SUCCESS);
expect(successAction).to.deep.equal(
Actions.actions.success({ data: expectedData })
);
});
});
This is of course an example of testing a saga that contains async actions. Generally when testing it is perferred to use sync mocks. In that case, there's no need to async/await.
Full example
Can be found under the examples
directory.
import chaiAsPromised from 'chai-as-promised';
import { call, take, put } from 'redux-saga/effects';
import SagaTester from 'redux-saga-tester';
chai.use(chaiAsPromised);
const someValue = 'SOME_VALUE';
const someResult = 'SOME_RESULT';
const someOtherValue = 'SOME_OTHER_VALUE';
const middlewareMeta = 'MIDDLEWARE_TEST';
const fetchRequestActionType = 'FETCH_REQUEST'
const fetchSuccessActionType = 'FETCH_SUCCESS'
const initialState = { someKey : someValue };
const reducer = (state = someValue, action) =>
action.type === fetchSuccessActionType ? someOtherValue : state;
const middleware = store => next => action => next({
...action,
meta : middlewareMeta
});
const options = { onError => console.error.bind(console) }
const fetchApi = () => someResult;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
function* listenAndFetch() {
yield take(fetchRequestActionType);
const result = yield call(fetchApi);
yield call(delay, 500);
yield put({ type : fetchSuccessActionType, payload : result });
}
it('Showcases the tester API', async () => {
const sagaTester = new SagaTester({
initialState,
reducers : { someKey : reducer },
middlewares : [middleware],
options,
});
sagaTester.start(listenAndFetch);
expect(sagaTester.getState()).to.deep.equal(initialState);
sagaTester.dispatch({type : fetchRequestActionType});
await sagaTester.waitFor(fetchSuccessActionType);
sagaTester.getCalledActions().forEach(action => {
expect(action.meta).to.equal(middlewareMeta)
});
expect(sagaTester.getState()).to.deep.equal({
someKey : someOtherValue
});
sagaTester.dispatch({ type : fetchRequestActionType });
expect(sagaTester.numCalled(fetchRequestActionType)).to.equal(2);
expect(sagaTester.numCalled(fetchSuccessActionType)).to.equal(1);
sagaTester.reset(true);
expect(sagaTester.wasCalled(fetchRequestActionType)).to.equal(false);
sagaTester.dispatch({ type : fetchRequestActionType });
expect(sagaTester.wasCalled(fetchRequestActionType)).to.equal(true);
})
API
new SagaTester(options) => sagaTester
Create a new SagaTester instance.
options: Object
initialState : Object
reducers : Object | Function
middlewares : Array[Function]
combineReducers : Function
ignoreReduxActions : Boolean
options : Object
- Options for
createSagaMiddleware
(see docs).
sagaTester.start(saga, [...args])
Starts execution of the provided saga.
saga : Function
- The saga generator function to start
[...args] : Any
- Optional Arguments to pass to the generator on start
sagaTester.dispatch(action)
Dispatches an action to the redux store.
sagaTester.updateState(newState)
Assigns the newState
into the current state.
(Only works with the default reducer.)
sagaTester.getState() => Object
Returns the state of the redux store.
sagaTester.waitFor(actionType, futureOnly) => Promise<action>
Returns a promise that will resolve if the specified action is dispatched to the store.
actionType : String
futureOnly : Boolean
- Causes waitFor to only resolve if the action is called in the future.
The promise resolves with the matching action
object.
sagaTester.wasCalled(actionType) => Boolean
Returns whether the specified was dispatched in the past.
sagaTester.numCalled(actionType) => Number
Returns the number of times an action with the given type was dispatched.
sagaTester.getLatestCalledAction() => Action
Returns the last action dispatched to the store.
sagaTester.getCalledActions() => Array[Actions]
Returns an array of all actions dispatched.
sagaTester.reset(clearActionList)
Reset the store state back to initialState
.
clearActionList : Boolean
- Clears the history of past actions (defaults to
false
).