redux-mock-store-await-actions
Waits for specific actions to be dispatched or a timeout expires.
NOTE: This module only works with redux-mock-store and shall only be used for testing purposes. Support for real Redux store is not provided.
Installation
$ npm install redux-mock-store-await-actions --save-dev
Motivation
Consider the following example:
function login(username, password) {
return async (dispatch) => {
dispatch({ type: 'LOGIN_START', payload: { username, password } });
try {
const user = await fetch('/login', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({ username, password })
});
dispatch({ type: 'LOGIN_SUCCESS', payload: user });
} catch (err) {
dispatch({ type: 'LOGIN_FAIL', payload: err });
throw err;
}
dispatch(fetchOrders());
}
}
function fetchOrders() {
return async (dispatch) => {
dispatch({ type: 'FETCH_ORDERS_START' });
try {
const orders = await fetch('/account/orders');
dispatch({ type: 'FETCH_ORDERS_SUCCESS', payload: orders });
} catch (err) {
dispatch({ type: 'FETCH_ORDERS_FAIL', payload: err });
throw err;
}
}
}
store.dispatch(login('my-username', 'my-password'));
expect(store.getActions()).toContain([
'LOGIN_START',
'FETCH_ORDERS_SUCCESS'
]);
The assertion above will fail because FETCH_ORDERS_SUCCESS
will not yet exist in the stack of actions.
To solve this, one can use setTimeout
explicitly in each test:
store.dispatch(login('my-username', 'my-password'));
setTimeout(() => expect(store.getActions()).toContain([
'LOGIN_START',
'FETCH_ORDERS_SUCCESS'
]), 50);
However, this is not pretty and is error-prone. redux-mock-store-await-actions
makes this easier for you.
Usage
Example #1: action types
Supply the action types to await for.
import waitForActions from 'redux-mock-store-await-actions';
import configureStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
const store = configureStore([thunkMiddleware])();
store.dispatch(login('my-username', 'my-password'));
await waitForActions(store, ['LOGIN_START', 'FETCH_ORDERS_SUCCESS']);
Example #2: action objects
Supply the action objects to await for, matching a subset of the properties of the dispatched actions. It performs a deep comparison between property values of dispatched and expected actions to determine whether the expected actions are partially contained in the stack of dispatched actions.
import waitForActions from 'redux-mock-store-await-actions';
import configureStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
const store = configureStore([thunkMiddleware])();
store.dispatch(login('my-username', 'my-password'));
await waitForActions(store, [
{
type: 'LOGIN_START',
payload: { username: 'my-username' },
},
{
type: 'FETCH_ORDERS_SUCCESS',
},
]);
API
waitForActions(store, actions, [options])
Returns a Promise
which fulfills if all actions
are dispatched before the timeout expires. The Promise
has a .cancel()
function which, if called, will reject the Promise
.
The Promise
might be rejected:
- as a result of timeout expiration, throwing
TimeoutError
- as a result of
.cancel()
invocation, throwing CancelledError
- when the action's matcher throws
MismatchError
NOTE: Subsequent calls to waitForActions
with the same actions should be preceded by a call to store.clearActions()
, otherwise the returned Promise
will resolve immediately.
store
Type: Object
The redux-mock-store
.
actions
Type: Object
String
Array
Function
The actions to wait for. It can be either:
String
: an action type string.Object
: an action object.Array
of either
- action objects;
- action type strings;
- action objects mixed with action type strings.
options
timeout
Type: Number
Default: 50
The timeout given in milliseconds.
matcher
Type: Function
Default: .matchers.order
Supplies custom behavior to specify how expected and dispatched actions should be compared. The function accepts two arguments: the array of expected actions and dispatched actions.
The matcher must either:
- return
true
to indicate a match has occurred and fulfill the Promise
- return
false
to indicate a match is yet to occur and the Promise
remains in pending state - throw
MismatchError
to indicate a match will not occur anymore and reject the Promise
Two built-in matchers are already shipped and available under .matchers
property:
order
matcher performs a comparison between the specified order of expected actions against the order of arrival of dispatched actions. On the first mismatch detected, MismatchError
is thrown for early rejectioncontaining
matcher is a less strict matcher which checks whether expected actions are contained within dispatched actions
Both matchers perform a partial deep comparison between dispatched and expected actions, as per Lodash's isMatch().
Example of a custom matcher implementation:
import waitForActions from 'redux-mock-store-await-actions';
import configureStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
const store = configureStore([thunkMiddleware])();
const expectedActions = [
{ type: 'LOGIN_START', payload: { username: 'my-username' } },
{ type: 'FETCH_ORDERS_SUCCESS' }
];
store.dispatch(login('my-username', 'my-password'));
waitForActions(store, expectedActions, { matcher: (expectedActions, storeActions) => {
const hasLoginFail = storeActions.some((action) => action.type === 'LOGIN_FAIL');
if (hasLoginFail) {
throw new waitForActions.MismatchError();
}
const hasLoginStart = storeActions.some((action) => action.type === 'LOGIN_START' && action.payload.username === 'my-username');
const hasFetchOrdersSuccess = storeActions.some((action) => action.type === 'FETCH_ORDERS_SUCCESS');
return hasLoginStart && hasFetchOrdersSuccess;
}})
.then(() => {
})
.catch((err) => {
});
Tests
$ npm test
$ npm test -- --watch
during development
License
MIT License