How to unit test this reductions?

So, I have an Action Redux creator that uses redux thunk middleware:

accountDetailsActions.js:

 export function updateProduct(product) { return (dispatch, getState) => { const { accountDetails } = getState(); dispatch({ type: types.UPDATE_PRODUCT, stateOfResidence: accountDetails.stateOfResidence, product, }); }; } 

How to check it? I use the chai package for testing. I found some resources on the Internet, but I'm not sure how to do this. Here is my test:

accountDetailsReducer.test.js:

 describe('types.UPDATE_PRODUCT', () => { it('should update product when passed a product object', () => { //arrange const initialState = { product: {} }; const product = { id: 1, accountTypeId: 1, officeRangeId: 1, additionalInfo: "", enabled: true }; const action = actions.updateProduct(product); const store = mockStore({courses: []}, action); store.dispatch(action); //this is as far as I've gotten - how can I populate my newState variable in order to test the `product` field after running the thunk? //act const newState = accountDetailsReducer(initialState, action); //assert expect(newState.product).to.be.an('object'); expect(newState.product).to.equal(product); }); }); 

My thunk does not perform any asynchronous actions. Any tips?

+11
javascript redux chai
source share
4 answers

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.

  // This is only an example to create asynchronism and record time taken function updateUser(){ return new Promise( // Returns a promise will be fulfilled after a random interval function(resolve, reject) { window.setTimeout( function() { // We fulfill the promise with the time taken to fulfill resolve(thisPromiseCount); }, Math.random() * 2000 + 1000); } ) }) 

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) }) }) }) 
+11
source share

Take a look at the Recipe: Writing Tests from the Official Documentation. Also, what are you testing, an action creator or reducer?

Action Creator Test Example

 describe('types.UPDATE_PRODUCT', () => { it('should update product when passed a product object', () => { const store = mockStore({courses: []}); const expectedActions = [ / * your expected actions */ ]; return store.dispatch(actions.updateProduct(product)) .then(() => { expect(store.getActions()).to.eql(expectedActions); }); }); }); 

Gear Test Example

Your gearbox should be a pure function so that you can test it in isolation outside the store environment.

 const yourReducer = require('../reducers/your-reducer'); describe('reducer test', () => { it('should do things', () => { const initialState = { product: {} }; const action = { type: types.UPDATE_PRODUCT, stateOfResidence: // whatever values you want to test with, product: { id: 1, accountTypeId: 1, officeRangeId: 1, additionalInfo: "", enabled: true } } const nextState = yourReducer(initialState, action); expect(nextState).to.be.eql({ /* ... */ }); }); }); 
+6
source share

To test asynchronous actions, it is recommended to use the redux-thunk-tester module.

A source.

0
source share
 export const someAsyncAction = (param) => (dispatch, getState) => { const { mock } = getState(); dispatch({ type: 'SOME_TYPE', mock: mock + param, }) } it('should test someAsyncAction', () => { const param = ' something'; const dispatch = jest.fn().mockImplementation(); const getState = () => ({ mock: 'mock value', }); const expectedAction = { type: 'SOME_TYPE', mock: 'mock value something' }; const callback = someAsyncAction(param); expect(typeof callback).toBe('function'); callback.call(this, dispatch, getState); expect(dispatch.mock.calls[0]).toEqual([expectedAction]) }); 
0
source share

All Articles