How to test that Redux's dispatch() has been called in React Component (Jest + Enzyme)

Viewed 17663

I have a lot of functional React components that call dispatch() (Redux). The dispatch() function itself is passed in by react-redux's useDispatch() hook.

As a simplified example:

const LogoutButton: FC = () => {
  const dispatch: Dispatch = useDispatch();
  
  return (
   <button onClick={() => {
     console.log("Clicked...") // To check that onClick() simulation is working
     dispatch(authActions.logout())  
   }}>
   LOGOUT
   </button> 
  )
}

Using Jest and Enzyme, what do I need to do to be able to assert expect(dispatch).toHaveBeenCalledWith(authActions.logout)?

I don't have a function for mocking the store or use redux-mock-store. Instead, I wrap the components in a Root component I made for testing. It's the same as my real Root component, but takes props for setting up the test's initial store (and history.location):

const TestRoot: FC<RootProps> = ({ children, initialState = {}, initialEntries = defaultLocation }) => {
  const store = createStore(reducers, initialState, applyMiddleware(thunk));

  return (
    <Provider store={store}>
      <MemoryRouter initialEntries={initialEntries}>
        <ScrollToTop />
        <StylesProvider jss={jss}>
          <ThemeProvider theme={theme}>{children}</ThemeProvider>
        </StylesProvider>
      </MemoryRouter>
    </Provider>
  );
};

which is used in tests to set up the Enzyme-wrapped component like:

wrappedLogoutButton = mount(
  <TestRoot initialState={initialState}>
    <LogoutButton />
  </TestRoot>
);

This set up works nicely for me (so far) and I don't want to change it if I don't have to. I did try injecting a redux-mock-store mock store into TestRoot, but that messed up every single test suite I've written.

I've tried numerous ways of mocking or spying on both dispatch() and useDispatch() but I haven't been able to see the mock being called. The onClick() simulation is working because I can see "Clicked..." being logged. Here's an example test (real code, not simplified example):

test('should log learner out', () => {
    const event = {
      preventDefault: () => {},
      target: { textContent: en.common['log-out-title'] } as unknown,
    } as React.MouseEvent<HTMLButtonElement, MouseEvent>;

    const wrappedLogOut = wrappedMenuDrawer.find(ListItem).at(3).find('a');

    // @ts-ignore
    act(() => wrappedLogOut.prop('onClick')(event));

    // TODO: test authService.logout is called
    // assert that dispatch was called with authActions.logout()
    expect(amplitude.logAmpEvent).toHaveBeenCalledWith('from main menu', { to: 'LOGOUT' }); // This assertion passes
  });

Methods/variations I've tried of mocking/spying on dispatch, based on documentation, Medium posts and similar questions on Stack Overflow, include:

import * as reactRedux from 'react-redux'
const spy = jest.spyOn(reactRedux, 'useDispatch'
import store from '../../store';
const spy = jest.spyOn(store, 'dispatch')
const mockUseDispatch = jest.fn();
const mockDispatch = jest.fn();

jest.mock('react-redux', () => ({
  useDispatch: () => mockUseDispatch.mockReturnValue(mockDispatch),
}));

// or...

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

mockUseDispatch.mockReturnValue(mockDispatch) // inside the actual test
  
1 Answers

There are multiple ways you can do so. AFAIK:

  • You can mock useDispatch hook to return your own fake dispatch function. I used in my test code for my own React library to help with managing binding dispatch to actions. Here it is hooks.test.tsx.

  • Since you are wrapping your component with a test React store, you can also replace store.dispatch with a fake function directly. See here for a discussion.

Benefit of the first one is that you can easily mock more stuff from react-redux as needed.

Related