0

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);
      });
  });
}
Andrew Korin
  • 294
  • 3
  • 17
  • Please could you post the `handleFavorite` action creator code? – A Jar of Clay Aug 27 '18 at 12:47
  • @A Jar of Clay, sure. – Andrew Korin Aug 27 '18 at 13:31
  • It looks like the action creator is expecting some data, but you don't pass this into the function in the test. To be sure whether this is the problem, the `followApi.handleFavorite` code is needed! – A Jar of Clay Aug 27 '18 at 13:37
  • @A Jar of Clay, done. Do you mean to pass some data into the `actions.handleFavorite()`? – Andrew Korin Aug 27 '18 at 13:56
  • Not passing in any data is not necessarily the issue. From the code, the error happens somewhere in `followApi.handleFavorite`, but it is hard to tell where. Perhaps try to log each step that happens inside that function and the sub-functions, to see whether that helps. – A Jar of Clay Aug 27 '18 at 16:49
  • @A Jar of Clay, after 100500 console logs I found out where is the problem. I used real HTTP service of my project and had to create mocked to get the test work properly. Now I have to use this `mockAxios.patch()` instead of `httpService.patch()` but what if I want to work both? The first one when the action is being tested and the second when there is a real request? What would you suggest? – Andrew Korin Aug 27 '18 at 21:31
  • No, that's not a good way to do it. Your current code looks like it should work, so without trying it out, I can't really help you further. I personally use the built in jest mock functions instead of tools like `moxios`, so you could try that, for example doing something like I suggest in another answer here: https://stackoverflow.com/a/51654713/7470360 – A Jar of Clay Aug 28 '18 at 19:35

1 Answers1

1

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

Lin Du
  • 88,126
  • 95
  • 281
  • 483