skip to Main Content

I have a following action, it’s an axios call with curried dispatch.

Note: originally, we wouldn’t need to curry the action call like that, but let’s say there is a situation where I need to make it curried (code below is just a made-up example).

./src/store/Auth/auth.action.ts

import { Dispatch } from 'redux';
import axios from 'axios';

export default {
    getUsersData: () => (dispatch: Dispatch) => {
        return axios.get('https://jsonplaceholder.typicode.com/users').then((result) => {
            dispatch({
                type: 'SET_USERS',
                payload: result
            });

            return result;
        })
            .catch((e) => {
                console.error(e);
            });
    },
}

./src/screens/Testing/index.tsx

import React, { useCallback } from 'react';
import { View, Button } from 'react-native';
import { useDispatch } from 'react-redux';

import Action from '../../store/Auth/auth.action';

const useAppDispatch = (action: any) => {
    const dispatch = useDispatch();

    return useCallback((param?: any, callback?: any) => dispatch(action(param, callback)), [dispatch, action]);
};

const Testing = () => {
    const getUsersDataAction = useAppDispatch(Action.getUsersData);

    return (
        <View>
            <Button
                title='hit me'
                testID='hit_me_id'
                onPress={ () => getUsersDataAction() }
            />
        </View>
    );
};

export default Testing;

action.test.js

import * as React from 'react';
import * as ReactRedux from 'react-redux';
import axios from 'axios';
import { render, fireEvent, waitFor } from '@testing-library/react-native';

import { Testing } from '../../src/screens';

describe('Get users data', () => {
    it('should return correct payload', async () => {
        const mockDispatch = jest.fn();

        jest.spyOn(ReactRedux, 'useDispatch').mockReturnValue(mockDispatch);

        axios.get.mockResolvedValue({});

        const screen = render(
            <Testing />
        );

        await waitFor(() => {
            expect(screen.getByTestId('hit_me_id')).toBeTruthy();
        });

        await waitFor(() => {
            fireEvent.press(screen.getByTestId('hit_me_id'));
        });

        expect(mockDispatch).toHaveBeenCalledTimes(1);
        expect(axios.get).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users');
    });
});

I got a following result:
enter image description here

Expected result is:
enter image description here

I feel the mock declaration is still not right.

const mockDispatch = jest.fn();

jest.spyOn(ReactRedux, 'useDispatch').mockReturnValue(mockDispatch);

Have tried with some other ways like:

const mockDispatch = () => jest.fn();
const mockDispatch = jest.fn(() => () => null);

jest.spyOn(ReactRedux, 'useDispatch').mockReturnValue(mockDispatch();
jest.spyOn(ReactRedux, 'useDispatch').mockReturnValue(() => mockDispatch();

but still not found the solution.

Does anyone know how to mock a curried function with Jest?

Many thanks in advance!

2

Answers


  1. First, I suggest you to spy the axios request too in this way:

    const axiosSpy = jest.spyOn(axios, 'get')
    axiosSpy.mockReturnValue({})
    expect(axiosSpy).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users');
    

    Then, since is the toHaveBeenCalledWith function that is failing, meaning that expect(mockDispatch).toHaveBeenCalledTimes(1) is passing, the expectation here is that the arguments are not resolved (called) since we are using a mocked function for dispatch .

    In this scenario I suggest you to mock the implementation of dispatch to call the action:

    mockDispatch.mockImplementation((action) => action()) //Add correct arguments
    
    Login or Signup to reply.
  2. Disclaimer: I haven’t used Redux for a long time, so my insight about the Redux part might not be 100% accurate regarding the part of calling actions / action creators.

    The solution to the question

    It seems that the missing part is mocking the behaviour of your mockDispatch function.

    TL;DR: Mock your mockDispatch implementation so it calls the getUsersData function from your actions.

    Explanation:

    This is what your code does:

    1. You obtain a getUsersDataAction function with your
      useAppDispatch hook. This returns a function that will be called
      on press.
    2. Press event happens. getUsersDataAction is called.
    3. You get the dispatch function by calling useDispatch.
    4. The dispatch function is called with your action, what does whatever it has to do (calling axios in this case).

    This is what your test code does:

    1. Press event happens. getUsersDataAction is called.
    2. Given useDispatch is called but you’ve mocked its return value, mockDispatch is returned.
    3. mockDispatch is called. mockDispatch is just jest.fn(); no mock resolved values or mock implementations have been defined for it (or not an implementation that does anything in particular in your later approaches), so nothing is actually done in this call.
    4. The actual getUsersData from your actions file (the one who actually calls axios) is never run because nothing calls it.

    My suggestion is to mock the implementation of mockDispatch in a way that actually calls your action:

    const mockDispatch = jest.fn(() => Action.getUsersData()(jest.fn())) // Another jest.fn just because your action expects a dispatch argument.
    

    My approach as an engineer

    Don’t test everything as a whole. You can assume that Redux is a well tested library, so call the parts actually done by you in a separate way:

    • Test your view until the Redux part comes in (in your case, until mockDispatch is called).
    • Test your action separately with unit tests in order to make sure axios is called the way you expect.
    • The in-between part is Redux’s job. Redux is well maintained and we should safely assume it works right.

    I understand you can be interested in a more integrated approach that proves both, but I’d leave that for an e2e test. Integration tests made with tools more focused on unit tests (what is Jest’s case) that also selectively mocks in-between stuff are harder to reason about. If a test is going to be expensive to read and maintain, let better make it an e2e test.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search