How the Redux Thunks Unit Test
The whole point of the creator of Thunk Action is to send asynchronous actions in the future. When using redux-thunk, a good approach is to simulate an asynchronous stream of start and end leading to success or error with three actions.
Although Mocha and Chai are used for testing in this example, you can just as easily use any assertion library or test environment.
Modeling an asynchronous process with multiple actions controlled by our main creator, Thunk Action
For example, suppose you want to perform an asynchronous operation that updates a product, and you want to know three important points.
- When asynchronous operation begins
- When the asynchronous operation ends
- Was the asynchronous operation successful or unsuccessful
So, it's time to model our reduction actions based on these stages of the operation life cycle. Remember that the same applies to all asynchronous operations, so this usually applies to http requests to receive data from the API.
We can write our actions like this.
accountDetailsActions.js:
export function updateProductStarted (product) { return { type: 'UPDATE_PRODUCT_STARTED', product, stateOfResidence } } export function updateProductSuccessful (product, stateOfResidence, timeTaken) { return { type: 'PRODUCT_UPDATE_SUCCESSFUL', product, stateOfResidence timeTaken } } export function updateProductFailure (product, err) { return { product, stateOfResidence, err } } // our thunk action creator which dispatches the actions above asynchronously export function updateProduct(product) { return dispatch => { const { accountDetails } = getState() const stateOfResidence = accountDetails.stateOfResidence // dispatch action as the async process has begun dispatch(updateProductStarted(product, stateOfResidence)) return updateUser() .then(timeTaken => { dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken)) // Yay! dispatch action because it worked } }) .catch(error => { // if our updateUser function ever rejected - currently never does - // oh no! dispatch action because of error dispatch(updateProductFailure(product, error)) }) } }
Pay attention to the busy activities below. This is our creator of Thunk Action. Since it returns a function, this is a special action that is intercepted by the redux-thunk middleware. The creator of this action may send other action creators in the future. Pretty smart.
Now we have written actions to simulate an asynchronous process, which is a user update. Suppose this process is a call to a function that returns a promise, as would be the most common approach to working with asynchronous processes today.
Define the logic for the actual asynchronous operation that we model using redundancy actions
For this example, we simply create a generic function that returns a promise. Replace this with an actual function that updates users or runs asynchronous logic. Make sure the function returns a promise.
We will use the function defined below to create a working standalone example. To get a working example, just add this function to your action file so that it is in the action field of your Thunk Action creator.
Our test file
import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import chai from 'chai' // You can use any testing library let expect = chai.expect; import { updateProduct } from './accountDetailsActions.js' const middlewares = [ thunk ] const mockStore = configureMockStore(middlewares) describe('Test thunk action creator', () => { it('expected actions should be dispatched on successful request', () => { const store = mockStore({}) const expectedActions = [ 'updateProductStarted', 'updateProductSuccessful' ] return store.dispatch(fetchSomething()) .then(() => { const actualActions = store.getActions().map(action => action.type) expect(actualActions).to.eql(expectedActions) }) }) it('expected actions should be dispatched on failed request', () => { const store = mockStore({}) const expectedActions = [ 'updateProductStarted', 'updateProductFailure' ] return store.dispatch(fetchSomething()) .then(() => { const actualActions = store.getActions().map(action => action.type) expect(actualActions).to.eql(expectedActions) }) }) })