Testing useEffect and Redux Hooks using Enzyme
By: Aashish Manandhar
Tags: React Hook Useeffect, Redux, Enzyme, Useselector, Useeffect

Hi there! About two years ago I wrote a blog about testing React Function components with hooks using enzyme and here I am writing about the same with a new perspective.

TLDR; Here I’ll let you know of a way that I learnt to test the lifecycle hooks such as useEffect and redux hooks namely useSelector and useDispatch while shallow mounting components using enzyme .

If you’ve not read my previous article I suggest you’d read it as it covers topics such as how to test hooks such as useState and how to handle controlled and uncontrolled components which I won’t be covering here.

Preface 🌃

Back then when I wrote the previous article there wasn’t a way to test the lifecycle methods of the shallow mounted react function components since the lifecycle methods such as useEffect and useLayoutEffect aren’t executed when we use shallow from enzyme to test it. Thus I recommended using mount for testing the lifecycle methods for that purpose. I wasn’t happy with it since all of my test files were a hybrid of the shallow and mount methods and it essentially felt redundant.

A year later I received a comment from Mike to try using jest-react-hooks-shallow package if I wanted to test effect hooks. Back then I tried using it but I remember it not working somehow. Recently I thought of giving it a try again and this time it worked. So here I am writing about it as well and how you could embed it into your enzyme tests.

jest-react-hooks-shallow

(Skip this section if you’d not want to learn about implementation details)

What this package does is magically make even the shallow renderer run the effect lifecycle methods useEffect and useLayoutEffect making our coding lifecycle easier.

Setting it up is fairly easy using npm or yarn. One additional step you’d have to take is to initialize it in jest setup file where you’ve setup the enzyme adapter.

import enableHooks from 'jest-react-hooks-shallow';



// pass an instance of jest to `enableHooks()`

enableHooks(jest);

Once installed all of your shallow mounted components in test files would essentially try to run its effect hooks as stated in the package documentation. Here’s where I ran into a problem. My components weren’t running the effect hooks. After several hit and trials I realized that the effect hooks were only working when I shallow mounted them by wrapping them up within a HTML tag. So what I meant by that is that if

const wrapper = shallow(<Login/>);

isn’t working then try using shallow by wrapping it up in a div and then getting it by using dive() .

const wrapper = shallow(<div><Login/></div>);

const container = wrapper.find('Login').dive();

Then you could test the function components’ lifecycle.

You could also manually enable the effect hooks in desired spec files instead of enabling it globally by passing in additional parameter while setting up the jest-react-hooks-shallow library.

enableHooks(jest, { dontMockByDefault: true });

Then you’d need to be enable the effect hooks in test files by wrapping then up within withHooks() .

Writing Tests

Let us take an example of this small component which makes an AJAX request on mount to check if the user is logged in and renders an input field conditionally (if they are logged in) and validates the text field when the input field value changes.

Testing Component Mount Effect Hook

Even though the useEffect hook is now running with jest-react-hooks-shallow , I immediately ran into a problem with Async functions in the effect hooks. I was trying to mock axios by using jest jest.mock('axios') and even tried to integrate msw package to create a mock server. But the problem in both the implementations was that by the time the mock server returned a value, the test would have already exited. I tried adding setTimeout and using done() function to make sure that it would wait for the change to trigger but it just failed no matter what I did. It was always rendering the <>User is not logged in.</> component.

The mistake I did was I was trying to mock the server instead of mocking the function that abstracted the logic that would query the server for the value. So the workaround of this was pretty simple. What I did was move the functions that made the async requests to another file and mock the function instead.

Now that Async requests are separated out to a different file you could mock axios or use msw to add test specs for requests.js . Now to write test specs for MyComponent.jsx you’d mock the requests.js which would ensure prompt response thus fixing the issue.

Then the tests run as expected rendering the input field when the user is logged in ensuring that we have tested our component mount effect hook.

// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MyComponent: When the user is logged in should render the logged in elements 1`] = `"<input type=\\"text\\" value=\\"\\"/><div></div>"`; exports[`MyComponent: When the user is not logged in should render not logged in message 1`] = `"User is not logged in."`;

Passing tests ✅

Testing Component Update Effect Hook

Testing the component update effect hook is also fairly simple. We can simulate events and check for changes.

Here we ensured that the validateText() and doSomething() prop get executed when the text state gets updated.

Testing Redux Hooks

If you’re using react-redux and not using useSelector and useDispatch hooks you’re missing out. I’d recommend reading this article that I wrote on how to migrate from using connect to using the redux hooks. 🏴‍☠️

Let us consider this simple component where the logged in state is being fetched from the store and an action is being dispatched on mount.

In order to test the components using those hooks you’d need to mock them and the best way to do so is to do it in the test setup file where you setup the enzyme adapter. It would looks something like this after mocking react-redux .

Testing useSelector Hook

It is pretty straight forward to test the useSelector hook now since we’ve already mocked it. So we’d just add a mock implementation to return a desired value.

Testing useDispatch Hook

It is similar to testing the useSelector hook but the only key difference being that since useDispatch is a function that dispatches the function that we pass thus we need to have a mock implementation such that it returns a mock function and we test the mock function to test whether the actions have been dispatched.

Conclusion

Thus in this way I managed to write the test specs for the effect and redux hooks. To summarize:

Enzyme doesn’t support effect hooks by default while shallow mounting components. You’d need to add jest-react-hooks-shallow to ensure that the hooks run.

to ensure that the hooks run. Async requests should be abstracted from the component into a separate file such that the methods that make async requests can be stubbed or mocked while testing the component instead of mocking the server.

To test the component update useEffect hook you’d simply trigger state updates and check for effects in rendered elements.

Redux hooks can be tested by mocking them and their implementation.

Hoping this will be helpful to those who have been using Enzyme to write tests. 😇 Please let me know if you know of any simpler or better methods of writing shallow tests.