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