Skahrz Skahrz - 1 month ago 30
React JSX Question

React and jest mock module

I am creating an application in which I use redux and node-fetch for remote data fetching.

I want to test the fact that I am well calling the fetch function with a good parameter.

This way, I am using jest.mock and jasmine.createSpy methods :

it('should have called the fetch method with URL constant', () => {
const spy = jasmine.createSpy('nodeFetch');
spy.and.callFake(() => new Promise(resolve => resolve('null')));
const mock = jest.mock('node-fetch', spy);
const slug = 'slug';
actionHandler[FETCH_REMOTE](slug);
expect(spy).toHaveBeenCalledWith(Constants.URL + slug);
});


Here's the function that I m trying to test :

[FETCH_REMOTE]: slug => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
await fetch(Constants.URL + slug);
addLocal();
};
}


AS you can see, I am trying to log the console.log(fetch()) behavior, and I am having the default promise to resolve given by node-fetch, and not the that I've mock with Jest and spied with jasmine.

Do you have an idea what it doesn't work ?

EDIT : My test displayed me an error like my spy has never been called

Answer

Your action-handler is actually a action handler factory. In actionHandler[FETCH_REMOTE], you are creating a new function. The returned function taskes dispatch as a parameter and invokes the code you are showing.
This means that your test code will never call any function on the spy, as the created function is never invoked.

I think you will need to create a mock dispatch function and do something like this:

let dispatchMock = jest.fn(); // create a mock function
actionHandler[FETCH_REMOTE](slug)(dispatchMock);

EDIT:

To me, your actionHandler looks more like an actionCreator, as it is usually called in redux terms, though I personally prefer to call them actionFactories because that is what they are: Factories that create actions.
As you are using thunks(?) your actionCreater (which is misleadingly named actionHandler) does not directly create an action but another function which is invoked as soon as the action is dispatched. For comparison, a regular actionCreator looks like this:

updateFilter: (filter) => ({type: actionNames.UPDATE_FILTER, payload: {filter: filter}}),

A actionHandler on the other hand reacts to actions being dispatched and evaluates their payload.

Here is what I would do in your case:

Create a new object called actionFactories like this:

const actionFactories = {

    fetchRemote(slug): (slug) => {
        return async dispatch => {
            dispatch(loading());
            console.log(fetch()); // Displays the default fetch promise result
            let response = await fetch(Constants.URL + slug);

            var responseAction;
            if (/* determine success of response */) {
                responseAction = actionFactories.fetchSuccessful(response);
            } else {
                responseAction = actionFactories.fetchFailed();
            }

            dispatch(responseAction);
        };
    }

    fetchFailed(): () => ({type: FETCH_FAILED, }),
    fetchSuccessful(response): () => ({type: FETCH_FAILED, payload: response })
};

Create an actionHandler for FETCH_FAILED and FETCH_SUCCESSFUL to update the store based on the response.

BTW: Your console.log statement does not make much sense too me, since fetch just returns a promise.