Dispatch is not a function - React Testing Library with React Redux Hooks

Viewed 12345

I want to test if the component is dispatching an action to the store, for this, i have to mock the functionalities of React Hooks.

I'm using the useDispatch hook to get the dispatcher function and put inside a variable so them i can call it.

When i run my test suits, even if the useDispatch function is mocked, it returns an error saying that it's not a function.

jest.mock('react-redux', () => ({
  useSelector: jest.fn(),
  useDispatch: jest.fn(() => {}),
}));

it('should be able open modal', () => {
  const { getByTestId } = render(<Dropdown />);

  fireEvent.click(getByTestId('dropdown'));
  fireEvent.click(getByTestId('button-Transactions'));

  const dispatch = jest.fn();
  useDispatch.mockReturnValue(dispatch);

  expect(dispatch).toHaveBeenCalledWith(openModal('transactions'));
});

The error:

    TypeError: dispatch is not a function

      19 | 
      20 |     if (item.action) {
    > 21 |       dispatch(item.action);
         |       ^
      22 |     }
      23 | 
      24 |     return item;

My component:

const history = useHistory();
  const dispatch = useDispatch();

  function handleNavigation(item) {
    if (item.path) return history.push(item.path);

    if (item.action) {
      dispatch(item.action);
    }

    return item;
  }
2 Answers

The component was trying to execute a function that wasn't declared yet. We need to mock before of the render method

const dispatch = jest.fn();
useDispatch.mockReturnValue(jest.fn());

const dropdown = render(<Dropdown />);

It is because when you mocked it, you haven't specified a value.

I thought of two ways to do this:

  1. Having a real store in your test, so you can test the integration:
const mockedStore = { /* the things you need in store in your test */ };
const store = configureStore(mockedStore, browserHistory);

const { getByTestId } = render(<Provider store={store}><Dropdown /></Provider>);
  1. Mock your dispatch, but you ll end up having infinite issues with useSelector (especially if you have more than one useSelector in the tree you rendered).
import * as ReactRedux from 'react-redux';

// useDispatch returns a function which we are mocking here
const mockDispatch = jest.fn();

beforeAll(() => {
  ReactRedux.useDispatch = jest.fn().mockImplementation(() => mockDispatch);
});

beforeEach(() => {
  ReactRedux.useDispatch.mockClear();
});


expect(mockDispatch).toHaveBeenCalledWith(yourAction);

Please note that the real issue you will face is not with dispatch, but with useSelector. If you mock it, it will return the value you want. But what if you have more than one in your tree ? for that, I real store is necessary as far as I know.

Related