redux-test-belt
v1.0.3
Published
JavaScript Testing utilities for Redux, the easy way.
Downloads
1,435
Readme
Redux Test Belt
Redux-test-belt is a JavaScript testing utility for Redux that makes it easier to assert, isolate, manipulate, and traverse your store's output.
Redux-test-belt's API is meant to be flexible by mimicking / extending Redux's store functionality. Furthermore, Redux-test-belt is unopinionated regarding which test runner, assertion library or how your application is constructed.
Installation
To get started with Redux-test-belt, you can simply install it with NPM:
npm install --save-dev redux-test-belt
Redux-test-belt is currently compatible with all versions of Redux.
Table of Contents
Quickstart
Redux-test-belt uses store enhanchers in order to improve the testing mechanisms. Getting started is a simple as:
import initStore from 'redux-test-belt';
const mockStore = initStore();
const store = mockStore({});
store.dispatch({type: 'START'});
store.dispatch({type: 'START'});
console.assert(store.getActions().length === 2, ">> Error")
Redux-test-belt takes seconds to setup against your testing suite. The following example uses Ava as a test runner:
import initStore from 'redux-test-belt';
import { describe } from 'ava-spec';
import thunk from 'redux-thunk';
describe('e2e test', it => {
it.beforeEach(t => {
// Before store's creation, we need to bootstrap redux-test-belt
// Any additional middlewares may be passed along store's creation
t.context.mockStoreWithMiddleware = initStore([thunk]);
});
it.afterEach(t => {
delete t.context.mockStoreWithMiddleware;
});
it('dispatches increment action', t => {
// setup
const store = t.context.mockStoreWithMiddleware({});
const action = { type: 'INCREMENT' };
store.dispatch(action);
t.deepEqual(store.getActions(), [action]);
});
});
API
initStore
initStore(middlewares?: Object) => mockStoreWrapper
Arguments
middlewares
(Array of middlewares
[optional])
Description
initStore()
is a single entry point bootstrapping Redux-test-belt's store. It accepts any additional middlewares that
may be required by each test. initStore exposes a single constructor function.
Example usage
// Usage without middlewares
import initStore from '../src/modules/mockStore';
const storeInit = initStore();
// Usage with middlewares
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
const storeInit = initStore([thunk, promise]);
mockStore
mockStore(initialState = null : Object,
reducers = (currentState, action) => currentState: Function ,
blockCheck = (...args) => true : Function) => Store
Arguments
initialState
(any
[optional]) The initial state of the storereducers
(Function
[optional]) A reducing function that returns the next state tree, given the current state tree and an action to handle.combineReducers
may be used in order to invoke multiple reducers.blockCheck
(Function
[optional]) .blockCheck
is a predicate function used along with theblockMiddleware
. It is primarily used in order to limit down the range of the actions invoked during our application's testing.
Description
mockStore()
is an extended version of Redux's [createStore()](http://redux.js.org/docs/api/createStore.html)
. Using store enhancers it adds additional testing utilities combining separated middlewares. This is mainly the heart of each testing unit.
Example usage
// use mockStore with initial state
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({ state: 1, items:[] });
});
// use mockStore with initial state and a reducer
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
// reducer
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.payload]);
default:
return state;
}
};
const store = t.context.mockStore({}, reducer);
});
// use mockStore along with combineReducers
import initStore from 'redux-test-belt';
import { combineReducers } from 'redux';
import test from 'ava';
test(t => {
const storeInit = initStore();
// reducer
function firstReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.payload]);
default:
return state;
}
};
function secondReducer(state = [], action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state
}
};
const reducers = combineReducers({
firstReducer,
secondReducer
});
const store = t.context.mockStore({}, reducers);
});
// use mockStore with initial state, a reducer and a block checking predicate function
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
// reducer
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.payload]);
default:
return state;
}
};
function()
const store = t.context.mockStore({}, reducer);
});
Store utilities & helpers
mockStore()
returns a Redux store instance extended by several testing utilities. Most of the packed functionality is separated in isolated middlewares as well, providing a clean and powerful testing workflow.
store.dispatch
store.dispatch(action?: Object)
Arguments
action
(Object
) A plain object describing the change that makes sense for your application. Actions are the only way to get data into the store. As a result, any kind of data indicating a store change, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.
Description
dispatch()
is used to dispatch actions to the store. This is the only way to trigger a state change. The functionality provided from this method is identical to Redux's dispatch().
Example usage
// use mockStore with initial state
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({});
store.dispatch({ type: 'INCREMENT' });
});
store.getState
store.getState() => state
Description
getState()
returns the current state tree of your application.
It is equal to the last value returned by the store’s reducer. The functionality provided from this method is identical to Redux's getState().
Actions utilities
Introduction
In most cases testing a Redux application includes testing against the state of the store. In the other side, unit test may require checking the actions dispatched. Redux-test-belt comes with a set of helper functions providing an ease applying assertions on the action chain.
store.getActions
store.getActions() => [Object]
Description
store.getActions()
returns a list with all the actions dispatched against the store. Keep in mind, that the actions dispatched may be asynchronous, store.getActions()
keeps track on the actions based on time on when the actions are fired through the action creators.
Example usage
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({});
const actions = [
{ type: 'BAR' },
{ type: 'foo', payload: 'PAYLODED' }
];
store.dispatch(actions[0]);
store.dispatch(actions[1]);
t.deepEqual(store.getActions(), actions);
});
store.hasActions
store.hasActions(matcher? Object(s) || String(s) ) => Boolean
Arguments
matcher
(Object(s) || String(s)
) Using matcher you may filter down the actions dispatched on the store. A matcher may be one or more action objects deeply matching the action queue or even just the action type(s).
Description
In large scale applications testing against the actions dispatched may be tedious. Matching a list of actions usually requires an assertion library, like Chai and lots of configuration. Redux-test-belt is packed with the store.hasActions()
as utility helper.
Example usage
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({});
store.dispatch({ type: 'ACTION_1' });
store.dispatch({ type: 'ACTION_2', payload: 'PAYLODED' });
store.dispatch({ type: 'ACTION_3' });
// Τesting against a superset of action objects
t.true(store.hasActions(
{ type: 'ACTION_1' },
{ type: 'ACTION_2', payload: 'PAYLODED' }));
t.true(store.hasActions(
{ type: 'ACTION_3' },
{ type: 'ACTION_1' }));
});
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({});
store.dispatch({ type: 'ACTION_1' });
store.dispatch({ type: 'ACTION_2', payload: 'PAYLODED' });
store.dispatch({ type: 'ACTION_3' });
// Τesting against a superset of action types
t.true(store.hasActions('ACTION_1', 'ACTION_2'));
t.true(store.hasActions('ACTION_3', 'ACTION_2'));
});
store.clearActions
store.clearActions() => []
Description
To help a test suite DRY up any duplicated setup and teardown code, Redux-test-belt provides the store.clearActions()
method. In particular, store.clearActions()
clears up the actions recorded during the store's lifecycle. It may be used on global before & after hooks, in order to keep each test isolated and independent.
Example usage
import initStore from 'redux-test-belt';
import test from 'ava';
test(t => {
const storeInit = initStore();
const store = t.context.mockStore({});
store.dispatch({ type: 'ACTION_1' });
store.dispatch({ type: 'ACTION_2' });
t.true(store.hasActions('ACTION1', 'ACTION2'));
store.clearActions();
t.deepEqual(store.getActions(), []);
});
import initStore from 'redux-test-belt';
import test from 'ava';
test.beforeEach(t => {
// Mock the store before each test
t.context.storeInit = initStore();
t.context.store = t.context.mockStore({});
});
test.afterEach(t => {
// Clear all the actions after each test
t.context.store.clearActions();
});
test(t => {
t.context.dispatch({ type: 'ACTION_1' });
t.context.dispatch({ type: 'ACTION_2' });
t.true(t.context.hasActions('ACTION1', 'ACTION2'));
});
Async utilities
Introduction
Redux applications may use asynchronous action creators; testing asynchronous actions that will be processed by reducers synchronously it's hard to deal with, though. Redux-test-belt provides async helper methods in order to write serialized tests. Keep in mind that async action handling requires using a thunk middleware for Redux like redux-thunk.
store.getPromises
store.getPromises() => [Object]
Description
store.getPromises()
grabs all the actions dispatched on the store that are encapsulated as Promise objects.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test.serial(t => {
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(superAction()));
};
}
const storeInit = initStore([thunk]);
const store = t.context.mockStore({});
return store.dispatch(asynchronousAction())
.then(() => {
t.is(store.getPromises().length, 1);
});
});
store.getPending
store.getPending() => [Object]
Description
store.getPending()
returns all the actions dispatched on the store that are encapsulated as Promises and their initial state is not fulfilled, nor rejected yet.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.all([
dispatch(superAction())
])
};
}
const storeInit = initStore([thunk]);
const store = t.context.mockStore({});
// the middleware messes the actions clear them :)
store.dispatch(asynchronousAction())
t.is(store.getPending().length, 1);
});
store.getRejected
store.getRejected() => [Object]
Description
store.getRejected()
returns all the actions dispatched on the store that are encapsulated as Promises and yet settled.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test.serial(t => {
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.reject()
.then(() => dispatch(superAction()));
};
}
const store = t.context.mockStore({});
// the middleware messes the actions clear them :)
return store.dispatch(asynchronousAction())
.then(() => {
t.is(store.getPending().length, 0);
t.is(store.getRejected().length, 1);
t.is(store.getPromises().length, 1);
});
});
store.getResolved
store.getResolved() => [Object]
Description
store.getResolved()
returns all the actions dispatched on the store that are encapsulated as Promises and settled, or locked into a promise chain.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test.serial(t => {
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(superAction()));
};
}
const store = t.context.mockStore({});
// the middleware messes the actions clear them :)
return store.dispatch(asynchronousAction())
.then(() => {
t.is(store.getPending().length, 0);
t.is(store.getResolved().length, 1);
t.is(store.getPromises().length, 1);
});
});
store.clearPromises
store.clearPromises() => []
Description
To help a test suite DRY up any duplicated setup and teardown code, Redux-test-belt provides the store.clearPromises()
method. In particular, store.clearPromises()
clears up the recorded actions that are encapsulated as Promise objects during store's lifecycle. It may used on global before & after hooks in order to keep each test isolated and independent .
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test.serial(t => {
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(superAction()));
};
}
const store = t.context.mockStore({});
// the middleware messes the actions clear them :)
return store.dispatch(asynchronousAction())
.then(() => {
t.is(store.getResolved().length, 1);
t.is(store.getPromises().length, 1);
store.clearPromises();
t.is(store.getResolved().length, 0);
t.is(store.getPromises().length, 0);
});
});
Blocking utilities
Introduction
In complex applications where lots of actions take place testing might be bloated. Redux-test-belt provides a set of utilities which enable users to filter the actions dispatched, clean up any redundant actions as well as to prevent state's manipulation. Each blocked action is stored for later use and may be accessed on demand later. All the blocked actions are actually dispatched on the store in order to keep Redux in place, the action type is marked as ✋BLOCKED_ACTION
whilst the payload holds the very first action created.
Setup
As it was mentioned earlier, you may pass a predicate function as a parameter during mockStore's instantiation. Blocking prediction may take place, either or the state or the action that takes place.
import initStore from 'redux-test-belt';
const mockStore = initStore([]);
const reducer = (currentState, action) => currentState;
const blockCheck = (currentState, action) => {
return (action.type === 'BLOCKED') ? false : true;
}
const store = mockStore({},reducer, blockCheck);
store.dispatch({ type: 'BLOCKED' });
store.dispatch({ type: 'NON-BLOCKED' });
store.getBlocked
store.getBlocked() => [actions]
Description
store.getBlocked()
returns all the blocked actions filtered from the passed predicate function.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test.serial(t => {
const mockStore = initStore([]);
const reducer = (currentState, action) => currentState;
const blockCheck = (currentState, action) => {
return (action.type === 'BLOCKED') ? false : true;
}
const store = mockStore({},reducer, blockCheck);
store.dispatch({ type: 'BLOCKED' });
store.dispatch({ type: 'NON-BLOCKED' });
t.deepEqual(store.getBlocked(),[{ type: 'BLOCKED' }]);
});
store.clearBlocked
store.clearBlocked() => []
Description
store.clearBlocked()
clears all the blocked actions stored.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
const mockStore = initStore([]);
const reducer = (currentState, action) => currentState;
const blockCheck = (currentState, action) => false;
const store = mockStore({},reducer, blockCheck);
store.dispatch({ type: 'BLOCKED' });
t.deepEqual(store.getBlocked(),[{ type: 'BLOCKED' }]);
store.clearBlocked();
t.deepEqual(store.getBlocked(),[]);
});
store.hasBlocked
store.hasBlocked(matcher? Object(s) || String(s) ) => Boolean
Arguments
matcher
(Object(s) || String(s)
) Using matcher you may filter down the repository of blocked actions which is being retained inside the store's instance. A matcher may be one or more action objects deeply matching the blocked action queue or even just the action type(s).
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
const mockStore = initStore([]);
const reducer = (currentState, action) => currentState;
const blockCheck = (currentState, action) => false;
const store = mockStore({},reducer, blockCheck);
store.dispatch({ type: 'ACTION1' });
store.dispatch({ type: 'ACTION2', payload: 'PAYLODED' });
t.true(store.hasBlocked('ACTION1', 'ACTION2'));
store.dispatch({ type: 'ACTION3' });
t.true(store.hasBlocked('ACTION1', 'ACTION2', 'ACTION3'));
t.false(store.hasBlocked('whatever', 'ACTION1'));
});
Orphans utilities
Introduction
Redux-test-belt declares orphans as actions that don't differentiate or manipulate the actual state. Where lots of action are dispatched simultaneously this feature allows testing to narrow down the results, take action on dead code and improve debugging.
store.getOrphans
store.getOrphans() => [Object]
Description
store.getOrphans()
returns a list of actions that didn't change the state upon dispatching.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
function fakeDispather() {
return {
type: 'ADD_TODO'
}
}
const mockStore = initStore([]);
const store = mockStore({});
store.dispatch(fakeDispather());
store.dispatch(fakeDispather());
t.is(store.getOrphans().length, 2);
t.deepEqual(
store.getOrphans(),
[{ type: 'ADD_TODO' }, { type: 'ADD_TODO' }]);
});
store.hasOrphans
store.hasActions(matcher? Object(s) || String(s) ) => Boolean
Arguments
matcher
(Object(s) || String(s)
) Using matcher you may filter down the orphans' repository. A matcher may be one or more action objects deeply matching the action queue or even just the action type(s).
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
function fakeDispather() {
return {
type: 'ADD_TODO'
}
}
const mockStore = initStore([]);
const store = mockStore({});
store.dispatch(fakeDispather());
store.dispatch(fakeDispather());
t.is(store.hasOrphans('ADD_TODO'), true);
t.is(store.hasOrphans({type: 'ADD_TODO' }), true);
});
store.clearOrphans
store.clearOrphans() => []
Description
store.clearOrphans()
truncates the list of orphan actions.
Example usage
import initStore from 'redux-test-belt';
import thunk from 'redux-thunk';
import test from 'ava';
test(t => {
function fakeDispather() {
return {
type: 'ADD_TODO'
}
}
const mockStore = initStore([]);
const store = mockStore({});
store.dispatch(fakeDispather());
store.dispatch(fakeDispather());
t.is(store.getOrphans().length, 2);
t.deepEqual(
store.getOrphans(),
[{ type: 'ADD_TODO' },{ type: 'ADD_TODO' }]);
store.clearOrphans();
t.is(store.getOrphans().length, 0);
});
Middlewares
Introduction
Where default createStore() is used, it seems a bit redundant to write additional tests using Redux-test-belt's mockStore()
. Therefore, Redux-test-belt exports each module as a flexible middleware.
blockMiddleware
Associated methods
Example usage
import { createStore } from 'redux';
import { blockMiddleware, blockedActions, PREFIX_ACTION } from 'redux-test-belt';
const blocking = (currentState, action) => false;
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.payload ])
default:
return state
}
}
let store = createStore(reducer,
[ 'Use Redux' ],
applyMiddleware.apply(null, [blockMiddleware(blocking)])
);
store.dispatch({
type: 'ACTION',
text: 'Read the docs'
});
// Expose each method
console.log(blockedActions.getBlocked());
//Alias: blockedActions.get()
console.log(blockedActions.hasBlocked('ACTION'));
//Alias: blockedActions.has()
blockedActions.clearBlocked();
console.log(blockedActions.getBlocked());
console.log(PREFIX_ACTION);
actionsLoggerMiddleware
Associated methods
Example usage
import { createStore } from 'redux';
import { loggedActions, actionsLoggerMiddleware } from 'redux-test-belt';
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.payload ])
default:
return state
}
}
let store = createStore(reducer,
[ 'Use Redux' ],
applyMiddleware.apply(null, [actionsLoggerMiddleware])
);
store.dispatch({
type: 'ACTION',
text: 'Read the docs'
});
// Expose each method
console.log(loggedActions.getActions());
// Alias: loggedActions.get()
console.log(loggedActions.hasActions('ACTION'));
// loggedActions.has()
loggedActions.clearActions();
console.log(loggedActions.getActions());
orphansMiddleware
Associated methods
Example usage
import { createStore } from 'redux';
import { orphansMiddleware, orphans } from 'redux-test-belt';
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.payload ])
default:
return state
}
}
let store = createStore(reducer,
[ 'Use Redux' ],
applyMiddleware.apply(null, [orphansMiddleware])
);
store.dispatch({
type: 'ACTION',
text: 'Read the docs'
});
// Expose each method
console.log(orphans.getOrphans());
//Alias: orphans.get()
console.log(orphans.hasOrphans('ACTION'));
//Alias: orphans.has()
orphans.clearOrphans();
console.log(orphans.getOrphans());
promiseMiddleware
Associated methods
Example usage
import { createStore } from 'redux';
import { promiseMiddleware, promises } from 'redux-test-belt';
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.payload ])
default:
return state
}
}
function superAction() {
return {
type: 'ACTION_DISPATCHED'
};
}
function asynchronousAction() {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(superAction()));
};
}
let store = createStore(reducer,
[ 'Use Redux' ],
applyMiddleware.apply(null, [promiseMiddleware])
);
store.dispatch(asynchronousAction());
// Expose each method
console.log(promises.getPromises());
// Alias: promises.get();
console.log(promises.getPending());
console.log(promises.getRejected());
console.log(promises.getResolved());
promises.clear();
// Alias: promises.clearPromises();
console.log(promises.getPromises());