0

How is the proper way to unit test the following redux async action?

const client = contentful.createClient(clientConfig);

export const fetchNavigation = () => {
  return dispatch => {

    return client.getEntries({content_type: 'navigation'})
      .then((entries) => {
        console.log('All entries for content_type = navigation')
        dispatch(receiveNavigation(entries))
      })
      .catch(error => {
       console.log('Something went wrong');
       dispatch(fetchNavigationFailure(error));
      });   
  }
}

I don't know how to customise the web request response body performed by client.getEntries. I think that replacing the getEntries function with my own one would do the trick. However, I don't know where to start to do that.

Here is the unit test I wrote:

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

describe('fetchNavigation', () => {
  it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {

    // Here I should prepare the client.getEntries() returned promise

    const expectedBodyResponse = { includes: ['do something', 'yay!'] }
    const expectedActions = [
      { type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
    ]
    const store = mockStore({ todos: [] })

    return store.dispatch(actions.fetchNavigation())
      .then(() => { 
        expect(store.getActions()).toEqual(expectedActions)
      })
  })
})
Alvaro
  • 107
  • 1
  • 7

2 Answers2

0

IMO mocking getEntries (and probably createClient) seems to be the right way to do. :) It depends how you load the contentful sdk. As I see you're using ES Modules and Jasmine, right?

To mock the getEntries function you have to mock the createClient as the client is not accessible from within your test.

I think this this answer might be what you're looking for.

I just wrote down an example.

import contentful from 'contentful';

export const fetchNavigation = () => {
  return (dispatch) => {
    return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
      .getEntries({ content_type: 'navigation' })
      .then(() => {
        dispatch('yeah');
      })
      .catch(error => console.error('Something went wrong', error));
  };
};

import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';

describe('Contentful mocking', () => {
  it('should be possible to mock Contentful', (done) => {
    const client = { getEntries: () => { return Promise.resolve(); } };
    const spy = {
      fn: (value) => {
        expect(value).toBe('yeah');
        done();
      },
    };

    spyOn(contentful.default, 'createClient').and.returnValue(client);

    fetchNavigation()(spy.fn);
  });
});

I had to move the createClient call into the action itself, because otherwise I don't think it's possible to reach and mock it when it's hidden in the module scope. I then used the import * as contentful from 'contentful' to mock and overwrite the needed functionality and to have the flexibility to adjust everything to my needs.

The usage of the createClient feels a bit unfortunate for me. I'd probably restructure everything a bit and would pass the client as dependency of all the actions? This way the mocking would become way easier and when you also have several action modules, there is most probably no need to initialize the client several times?

Community
  • 1
  • 1
stefan judis
  • 3,416
  • 14
  • 22
0

I solved in this way. First I moved the creation of the client to its own file with functions initClient() and getClient(). The module is called contentfulClient.

Then, I found out that it is possible to mock the function of an instantiated object in sinon:

import * as contentful from './services/contentfulClient';

const client = contentful.initClient(clientConfig);
const navigation = {
  items: ['page1', 'page2']
};

// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);

// Assert
return store.dispatch(actions.fetchNavigation())
      .then( () => {     expect(store.getActions()).toEqual(expectedActions)
      })
Alvaro
  • 107
  • 1
  • 7