Testing React functional component using hooks useEffect, useDispatch and useSelector in shallow renderer with Jest + Enzyme Pylnata Follow Aug 28, 2019 · 4 min read

Please note that the story told in this article was kind of an experiment. In practice, using mount is better in general, as it brings us closer to real conditions and our tests become more reliable for our front-end functionality.

* * *

During mastering react hooks, I wrote a small open-source project that uses only hooks. And for completeness, also the hooks of the react-redux (useDispatch and useSelector) are used instead of the traditional connect, so it really keeps up with the latest trends. Everything was fine before my trying to write unit tests using the classic jest+enzyme set.

In particular, according to the checklist https://github.com/airbnb/enzyme/issues/2011 today is still no support useEffect in shallow renderer mode. So firstly, I decided to use a mount mode. But my discontent grew with each test, as I had to render for testing <Provider store={store}> and <BrowseRouter> and do another things. Also, Enzyme developers encourage us to use shallow mode as much as you can and I wanted to use it also to get tests for isolated units.

So I stopped, googled and found the article Shallow testing hooks with Enzyme. In the end, the approach was born, which allowes us to test functional components using react hooks and react-redux hooks only in a shallow mode. I want to emphasize also that my application was really simple, and most of components used useEffect with useDispatch and useSelector.

Goal

The main task that I faced is to write tests:

Component dispatches particular action to store ( redux store) on mount( inside useEffect hook) Component rerenders correct jsx according to store state.

Solution

Move your react-redux hooks to a single file:

2. In tested component import them like:

3. Replace your calls “useEffect(()=>{…}, […])” in code with “React.useEffect(()=>{…}, […])”. It is important as we plan to create mock for useEffect in our test.

And we ready to write test.

I will write test for component named RecipeList. It connects to redux store using useSelector hook, and dispatches actions to it using useDispatch hook from “react-redux”. It dispatches search() action, that I imported from “./actions.js”. And it renders list of RecipeItem components if useSelector returns us non empty array of recipes.

Important: in project from codesandbox for this article I uses middleware redux-thunk. But actually you can use another middleware. The point is to check that store was dispatched by expected action. In fact, in the unit test for Component we don’t need to test how action will be processed. It is job for tests for actions, epics/sagas, reducers. For example, if I use redux-saga or redux-observable as middleware, I always create mock store (with redux-mock-store) without any middlewares and just check that store was dispatched by correct action.

So we start test with imports:

And continue with implementation of test (see comments):

describe("RecipeList", () => {

let wrapper;

let useEffect;

let store; const mockUseEffect = () => {

useEffect.mockImplementationOnce(f => f());

}; beforeEach(() => {

/* mocking store */

store = configureStore([thunk])({

recipes: fakeRecipes,

isLoading: false,

error: null

}); /* mocking useEffect */

useEffect = jest.spyOn(React, "useEffect");

mockUseEffect(); // 2 times

mockUseEffect(); // /* mocking useSelector on our mock store */

jest

.spyOn(ReactReduxHooks, "useSelector")

.mockImplementation(state => store.getState()); /* mocking useDispatch on our mock store */

jest

.spyOn(ReactReduxHooks, "useDispatch")

.mockImplementation(() => store.dispatch); /* shallow rendering */

wrapper = shallow(<RecipeList store={store} />);

}); describe("on mount", () => {

it("dispatch search action to store", () => {

const actions = store.getActions();

expect(actions).toEqual([{ type: "SEARCH", query: "all" },

{ type: "SEARCH_SUCCESS", recipes: fakeRecipes }]);

});

}); it("should render RecipeItem components if recipes.length > 0",

() => {expect(wrapper.find(RecipeItem)).toHaveLength(3);

});

});

As we can see we create mocks for store, for useEffect, useSelector and useDispatch. And with all that we can test render our component in shallow mode and test two use cases that we need.

And as result:

You can see all code here:

More usage in this repo: https://github.com/pylnata/recipies-app