Search code examples
testingreact-reduxjestjsmoxios

err in a redux action test


I'm new to Jest testing and moxios. Just trying to write my first async action test. Test dies with this error:

Expected value to equal:
  [{"payload": {"checked": true, "followingInfoId": "1"}, "type": "HANDLE_FAVORITE_SUCCESS"}]
Received:
  [{"payload": [TypeError: Cannot read property 'getItem' of undefined], "type": "ERROR"}]

Does anyone can tell me where is the problem. I suppose that the moxios response doesn't go to "then"?

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import * as actions from './index';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore();

describe('followings actions', () => {
  beforeEach(() => {
    moxios.install();
    store.clearActions();
  });

  afterEach(() => {
    moxios.uninstall();
  });

  it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
    moxios.wait(() => {
      const request = moxios.requests.mostRecent();
      request.respondWith({
        status: 200,
        payload: {
          followingInfoId: '1',
          checked: true
        }
      });
    });

    const expectedActions = [
      {
        'type': 'HANDLE_FAVORITE_SUCCESS',
        payload: {
          followingInfoId: '1',
          checked: true
        }
      }
    ];

    return store.dispatch(actions.handleFavorite()).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });
});

Here is the action creator:

export const handleFavorite = data => {
  return dispatch => {
    return followApi.handleFavorite(data).then(payload => {
      dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
    }, err => {    
      dispatch({ type: 'ERROR', payload: err })
    });
  }
};

Here is the followApi.handleFavorite:

handleFavorite: (data) => {
    return new Promise ((resolve, reject) => {
      httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
        res => {
          if (res.data.payload) {
            resolve(res.data.payload);
          } else reject({status: 401});
        }, err => reject(err)
      );
    });
  },

And and a part of the http-servise if needed:

patch: (url, params) => {
  return new Promise((resolve, reject) => {
      axios(url, {
          method: 'PATCH',
          headers: getHeaders(),
          data: params
      }).then(res => {
          resolve(res);
      }, err => {
          reject(err);
      });
  });
}

Solution

  • If you want to test action creators, you should mock followApi.handleFavorite method rather than axios.

    Here is the solution for testing action creators only use jestjs and typescript, You can mock the module manually by yourself.

    Folder structure:

    .
    ├── actionCreators.spec.ts
    ├── actionCreators.ts
    ├── followApi.ts
    └── httpServise.ts
    

    actionCreators.ts:

    import followApi from './followApi';
    
    export const handleFavorite = data => {
      return dispatch => {
        return followApi.handleFavorite(data).then(
          payload => {
            dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
          },
          err => {
            dispatch({ type: 'ERROR', payload: err });
          }
        );
      };
    };
    

    followApi.ts:

    import { httpServise } from './httpServise';
    
    const host = 'http://github.com/mrdulin';
    const port = 3000;
    
    const followApi = {
      handleFavorite: data => {
        return new Promise((resolve, reject) => {
          httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
            (res: any) => {
              if (res.data.payload) {
                resolve(res.data.payload);
              } else {
                reject({ status: 401 });
              }
            },
            err => reject(err)
          );
        });
      }
    };
    
    export default followApi;
    

    httpService.ts:

    import axios from 'axios';
    
    function getHeaders() {
      return {};
    }
    
    export const httpServise = {
      patch: (url, params) => {
        return new Promise((resolve, reject) => {
          axios(url, {
            method: 'PATCH',
            headers: getHeaders(),
            data: params
          }).then(
            res => {
              resolve(res);
            },
            err => {
              reject(err);
            }
          );
        });
      }
    };
    

    actionCreators.spec.ts:

    import configureMockStore from 'redux-mock-store';
    import thunk, { ThunkDispatch } from 'redux-thunk';
    import { AnyAction } from 'redux';
    import * as actions from './actionCreators';
    import followApi from './followApi';
    
    jest.mock('./followApi.ts', () => {
      return {
        handleFavorite: jest.fn()
      };
    });
    
    type State = any;
    const middlewares = [thunk];
    const mockStore = configureMockStore<State, ThunkDispatch<State, undefined, AnyAction>>(middlewares);
    const store = mockStore();
    
    describe('followings actions', () => {
      beforeEach(() => {
        store.clearActions();
        jest.resetAllMocks();
      });
      it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
        expect.assertions(2);
        const mockedHandleFavoritePayload = {
          followingInfoId: '1',
          checked: true
        };
        (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockResolvedValueOnce(
          mockedHandleFavoritePayload
        );
        const data = 'jest';
        const expectedActions = [
          {
            type: 'HANDLE_FAVORITE_SUCCESS',
            payload: {
              followingInfoId: '1',
              checked: true
            }
          }
        ];
        return store.dispatch(actions.handleFavorite(data)).then(() => {
          expect(store.getActions()).toEqual(expectedActions);
          expect(followApi.handleFavorite).toBeCalledWith(data);
        });
      });
    
      it('dispatches the ERROR action', () => {
        const mockedhHndleFavoriteError = new Error('network error');
        (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockRejectedValueOnce(
          mockedhHndleFavoriteError
        );
        const data = 'jest';
        const expectedActions = [
          {
            type: 'ERROR',
            payload: mockedhHndleFavoriteError
          }
        ];
        return store.dispatch(actions.handleFavorite(data)).then(() => {
          expect(store.getActions()).toEqual(expectedActions);
          expect(followApi.handleFavorite).toBeCalledWith(data);
        });
      });
    });
    

    Unit test result with 100% coverage report:

    PASS  src/stackoverflow/52025257/actionCreators.spec.ts (5.95s)
      followings actions
        ✓ dispatches the HANDLE_FAVORITE_SUCCESS action (5ms)
        ✓ dispatches the ERROR action (2ms)
    
    -------------------|----------|----------|----------|----------|-------------------|
    File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    -------------------|----------|----------|----------|----------|-------------------|
    All files          |      100 |      100 |      100 |      100 |                   |
     actionCreators.ts |      100 |      100 |      100 |      100 |                   |
    -------------------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        6.87s, estimated 7s
    

    Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52025257