Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
  
like image 245
user680141 Avatar asked Jun 25 '20 05:06

user680141


People also ask

How do you test a reducer?

Test a reducer Reducers are so simple to test as they are pure functions. We just need to call the reducer function and pass in a fake piece of state and an action and then check the new state slice returns.

How do you test thunk actions?

We use store. getActions() to identify the actions that our thunk has dispatched when it was called. expectedActions is an array which holds the list of actions that the thunk should have called. We pass this to expect() and verify if the actions that the thunk has dispatched are the same as those we had anticipated.

How to test Redux action Dispatch with jest?

Now, for your second unit test, you will check with Jest whether a HTML button click will dispatch a specific Redux action. Therefore, you will have to introduce a spy for the Redux store's dispatch function and you will have to simulate a click event on the given button.

How to test the connected React-Redux component?

Ultimately, that's already it for testing the second part of the connected react-redux component: 1 Provide State -> React Component (Unit Test) => Component Renders 2 React Component (Unit Test) -> Simulate Event => Dispatch Action Triggers More ...

How do I report an issue in React/Redux testing?

You can help us out by using the "report an issue" button at the bottom of the tutorial. This is part 3 of a 4-part series on testing React / Redux apps using both Jest and Enzyme for a robust testing solution. In this part we’ll cover some simple examples on how to test Redux actions.

How to mock a redux store using jest?

* redux-mock-store — Used to mock our Redux store. If you check the script in package.json I have added ”test”: “jest” So to run the test all you have to do is And Jest will pick all the test files which are put under the folder __test__. Place all your test files under the folder __test__ as Jest by default picks all the test from here.


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.

like image 90
sidecus Avatar answered Nov 12 '22 16:11

sidecus