A testing utility for React-Redux apps that provides hooks to run tests after each redux action.
When you want to test a React component after an operation that dispatches a Redux action, you need to make it sure that before your test code goes on, all the React components connected to the Redux store are thoroughly updated based on the new state. We can easily assure it with act()
provided by React Test Utility or Testing Library for React.
However, it cannot help when a test target operation dispatches Redux actions asynchronously, like the thunk action below.
export const thunkCreators = {
requestMessages: () =>
(dispatch, getState) => {
// Synchronous action dispatch
dispatch(actionCreators.requestMessages())
return fetch('/api/inbox/messages', { method: 'GET' })
.then(response => response.json())
.then(json => {
// Asynchronous action dispatch
return dispatch(actionCreators.receiveMessages(json))
})
},
}
When you simulate an operation inside an act()
's callback to test the result, the test code after the act()
call may test a result of the first action, or possibly a result of the second one.
React-Redux Stethoscope can handle such a case. It can target specific Redux actions, and run test code after each action entirely takes effect on all the React components.
With React-Redux Stethoscope, test code against the Redux actions becomes like this.
it('display "Loading..." while fetching data.', async () => {
const stethoscope = new Stethoscope()
const store = createStore(
rootReducer,
undefined,
compose(applyMiddleware([ thunk, stethoscope.asMiddleware() ]), ...enhancers))
const wrapper = render(
<Provider store={store}>
<App />
</Provider>
)
await stethoscope.listenAsync({
act: () => {
wrapper.getAllByText('↺')[0].click()
},
targets: [
{
actionType: actionCreators.requestMessages().type,
onUpdated: () => {
expect(wrapper.queryAllByText('Loading...')).toHaveLength(1)
}
},
{
actionType: actionCreators.receiveMessages({}).type,
onUpdated: () => {
expect(wrapper.queryByText('Loading...')).toBeNull()
expect(wrapper.queryAllByText('Hello, World')).toHaveLength(1)
}
},
]
})
})
listenAsync()
function runs act
property of its parameter object over the act()
of React Test Utility, and waits for a Redux action to be dispatched. When the dispatched action matches one of the action types specified in the targets
property, Stethoscope calls back the function in the corresponding onUpdated
property. listenAsync()
keeps waiting until all the action types in the targets
are dispatched.
act
property of the object provided tolistenAsync()
function is simply passed toact()
of React Test Utility. So, other than simulating operations, you can also directly dispatch actions to your Redux store in the function of theact
property.
When the waiting time exceeds 3 seconds, Stethoscope throws an error. You can modify the timeout length by specifying timeout
property in the parameter object in milliseconds.
You can install this module via npm which is bundled with node, and it should be installed as one of your devDependencies
.
npm install --save-dev react-redux-stethoscope
You also can install it via yarn.
yarn add --dev react-redux-stethoscope
This library has peerDependencies
listings for react
, react-dom
and redux
.
React-Redux Stethoscope requires a set-up before your tests run with it. It can be done by just calling useStethoscope()
once. If you use Jest, then add this function call in setupTests.js or setupTests.ts.
Then, in your test code, instantiate Stethoscope and make sure to add the Redux middleware of Stethoscope when you create Redux store. The middleware is generated by asMiddleware()
function.
Now, Stethoscope is ready to call listenAsync()
to test each action dispatch.
You can also use Enzyme with React-Redux Stethoscope, to render react components and simulate operations. The set-up and usage of Stethoscope are all in the same manner.