loading...
Cover image for Moxios or Nock To Mock

Moxios or Nock To Mock

hawicaesar profile image HawiCaesar ・3 min read

I recently came across an interesting hurdle when testing async action creators. I was testing whether a delete action creator is called when the thunk runs. The code below shows a thunk and an action creator respectively.

/**
 * Delete user contribution thunk
 *
 * @param {string} contribution for the contribution to be deleted
 * @return {(dispatch) => Promise<TResult2|TResult1}
 */
export const deleteUserContribution = (contribution) => {
  return ((dispatch) => {
    return http.delete(`${config.API_BASE_URL}/contributions/${contribution.id}`)
      .then((response) => {
        dispatch(deleteUserContributionSuccess(contribution.id));
      });
  });
};

/**
 * Delete user contribution action creator
 *
 * @param {string} contributionId for the contribution to be deleted
 * @return {IDeleteUserContributionSuccess}
 */
export const deleteUserContributionSuccess = (contributionId): IDeleteUserContributionSuccess => {
  return { type: DELETE_USER_CONTRIBUTION_SUCCESS, contributionId };
};

I used nock at first to mock requests.

// third party libraries
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { expect } from 'chai';
import * as moxios from 'moxios';

// fixtures
import { sampleContribution } from '../fixtures/contribution';

// actions
import { deleteUserContribution } from './ContributionActions';

// types
import { DELETE_USER_CONTRIBUTION_SUCCESS }  from '../types';

const middleware = [thunk];
const mockStore = configureMockStore(middleware);
let store;
let url;

describe('Async Contribution Actions', () => {
 it('creates DELETE_USER_CONTRIBUTION_SUCCESS when deleting a contribution', () {
     nock(`${config.API_BASE_URL}`)
      .delete(`/contributions/${contribution.id}`)
      .reply(204,'')

     const expectedActions = [
     { type: DELETE_USER_CONTRIBUTION_SUCCESS, contributionId: sampleContribution.id },
];

    return store.dispatch(deleteUserContribution(sampleContribution)).then(() => {
      const actualActions = store.getActions();
      expect(actualActions).to.eql(expectedActions);
    });
  });
})

I ran the test and it passed. I then proceeded to make a PR for the delete feature and requested a review from my team. A team member who was also writing async action tests slacked me and pointed out that package.json already has Moxios and that I could leverage on that rather than install a new mock library. I decided to try it out. I followed the Moxios documentation and implemented it. However I got error below.

 1) Async Contribution Actions
       creates DELETE_USER_CONTRIBUTION_SUCCESS when deleting a contribution:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

I added the done() just after expect but the above error showed up.
I decided to slack the team member who encouraged me to use Moxios and before I even asked anything he had already sent me a message. The message detailed that he had pulled my branch and broke my test by changing the expect to be something like so

return store.dispatch(deleteUserContribution(sampleContribution)).then(() => {
      const actualActions = store.getActions();
      expect(actualActions).to.eql(1);
    });

and he told me the test was still passing. He also added that he had the same issue where nock seemed to not perform its work correctly therefore moved to Moxios. I was skeptical at first but I decided to investigate the matter. Lo and behold I found that nock was just resolving the promise and not returning the data I had told it to return. The only bit that still has me baffled was why the expect was still working. I implemented the test using Moxios and it worked as follows. The imports and middleware remained the same.

describe('Async Contribution Actions', () => {
  beforeEach(() => {
    moxios.install();
    store = mockStore({});
    url = `${config.API_BASE_URL}`;
  });
  afterEach(() => {
    moxios.uninstall();
  });

  it('creates DELETE_USER_CONTRIBUTION_SUCCESS when deleting a contribution', (done) => {
    moxios.stubRequest(`${url}/${sampleContribution.id}`, {
      status: 204,
      response: { data: '' },
    });

    const expectedActions = [
      { type: DELETE_USER_CONTRIBUTION_SUCCESS, contributionId: sampleContribution.id },
    ];

    store.dispatch(deleteUserContribution(sampleContribution)).then(() => {
      const actualActions = store.getActions();
      expect(actualActions).to.eql(expectedActions);
      done();
    });
  });
});

I tried to break the test by adding a false actual value and it failed meaning Moxios did a better job. I know the redux documentation uses fetch-mock but I suspect it was using nock before.

The question I probably have now for you is what do you use to test async action creators ?

Posted on by:

hawicaesar profile

HawiCaesar

@hawicaesar

Whether we use React or Vue or Angular, we will regret it. Instead let’s solve the problem then choose the technology Interested in UI/UX, design thinking and data visualization

Discussion

markdown guide
 

Have you faced the case of having multiple calls to moxios.stubRequest within one test case? At that point a funny thing happens whereby only the first call is made.