How do I test an async action creator that calls another action with setTimeout
Asked Answered
A

1

7

I have the following action that displays a notification and then removes it, and I´m trying to write a unit test for it but I can't seem to figure out how to mock setTimeout.

export const addNotification = (text, notificationType = 'success', time = 4000) => {
        return (dispatch, getState) =>{
            let newId = new Date().getTime();
            dispatch({
                type: 'ADD_NOTIFICATION',
                notificationType,
                text,
                id: newId
            });
            setTimeout(()=>{
                dispatch(removeNotification(newId))
            }, time)
        }
    };
    export const removeNotification = (id) => (
    {
        type: 'REMOVE_NOTIFICATION',
        id
    });

Following the tutorial in the redux website on async testing I came up with the following test:

    import * as actions from '../../client/actions/notifyActionCreator'
    import configureMockStore from 'redux-mock-store'
    import thunk from 'redux-thunk'

    const middlewares = [ thunk ];
    const mockStore = configureMockStore(middlewares);


    describe('actions', ()=>{

        it('should create an action to add a notification and then remove it', ()=>{

            const store = mockStore({ notifications:[] });

            const text = 'test action';
            const notificationType = 'success';
            const time = 4000;
            const newId = new Date().getTime();

            const expectedActions = [{
                type: 'ADD_NOTIFICATION',
                notificationType,
                text,
                id: newId
            },{
                type: 'REMOVE_NOTIFICATION',
                id: newId
            }];

            return store.dispatch(actions.addNotification(text,notificationType,time))
                .then(() => {
                    expect(store.getActions()).toEqual(expectedActions)
                });
        });
    });

right now it just throws an error Cannot read property 'then' of undefined at store.dispatch, any help would be greatly appreciated.

Accumulator answered 2/3, 2017 at 18:14 Comment(1)
Are you using jest?Endodontics
S
8

First of all, since your action creator does not return anything, when you call store.dispatch(actions.addNotification()) it returns undefined and that's why you are getting the error Cannot read property 'then' of undefined. To use .then() it should return a promise.

So you should either fix your action creator or the test to reflect what the action creator actually does. To make your test pass, you can change your tests to something like this:

// set up jest's fake timers so you don't actually have to wait 4s
jest.useFakeTimers();

store.dispatch(actions.addNotification(text,notificationType,time));
jest.runAllTimers();
expect(store.getActions()).toEqual(expectedActions);

Another option would be to use the strategy detailed in the Jest docs.

// receive a function as argument
test('should create an action to add a notification and then remove it', (done)=>{

    // ...

    store.dispatch(actions.addNotification(text,notificationType,time));
    setTimeout(() => {
      expect(store.getActions()).toEqual(expectedActions);
      done();
    }, time);
});

When using this strategy, Jest will wait for done() to be called otherwise it will consider the test ended when finishing the execution of the test body.

Silverpoint answered 2/3, 2017 at 18:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.