Photo by chuttersnap on Unsplash

Let’s begin with the to-do list component from previous stories. I removed the class-based version of the component as we won’t need it anymore. I also let go of loading the initial state from external source and cleaned up the code a little, just to avoid unnecessary complexity.

Take a look at component and its code:

Setup Enzyme

(TL;DR exactly as usual)

We’ll need to install the new dependencies: yarn add -D enzyme enzyme-adapter-react-16 and create a new file, src/setupTests.js :

Since we used create-react-app and did not eject, there’s no need to update package.json (although we also cannot change the name or location of the setup file).

Add some tests

TL;DR Don’t use shallow (for now) and you’re good to go

Let’s start with something simple:

Now run yarn test and…

FAIL src/TodoList.spec.js

● TodoList › renders Invariant Violation: Hooks can only be called inside the body of a function component. 3 |

4 | export const TodoList = () => {

> 5 | const [todos, setTodos] = useState([

Not what we wanted. Well, first of all:

Update your React version

A quick look inside package.json reveals that we got stuck at React 16.7.0-alpha. Let’s update then ( yarn upgrade react react-dom ). If you’re on a newer version than 16.8.6 you might get lucky and not encounter the following problem:

FAIL src/TodoList.spec.js

● TodoList › renders

1. You might have mismatching versions of React and the renderer (such as React DOM)

2. You might be breaking the Rules of Hooks

3. You might have more than one copy of React in the same app

See Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:1. You might have mismatching versions of React and the renderer (such as React DOM)2. You might be breaking the Rules of Hooks3. You might have more than one copy of React in the same appSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem. 3 |

4 | export const TodoList = () => {

> 5 | const [todos, setTodos] = useState([

If you do however, it’s a quick fix. Turns out that there is a bug in React related to updating state in shallow wrappers, so let’s just replace shallow with mount and we’re good to go.

PASS src/TodoList.spec.js Test Suites: 1 skipped, 1 passed, 1 of 2 total

Tests: 1 skipped, 1 passed, 2 total

Snapshots: 0 total

Time: 2.116s

Ran all test suites related to changed files.

Add more tests

Run those tests

PASS src/TodoList.spec.js Test Suites: 1 skipped, 1 passed, 1 of 2 total

Tests: 1 skipped, 4 passed, 5 total

Snapshots: 0 total

Time: 2.176s

Ran all test suites related to changed files.

Everything is alright.

The only thing you cannot do

is to try and access the state:

FAIL src/TodoList.spec.js

● TodoList › will fail ReactWrapper::setState() can only be called on class components 47 | it("will fail", () => {

48 | const wrapper = mount(<TodoList />);

> 49 | wrapper.setState("todos", []);

which is pretty obvious after reading the error message (or after considering that there isn’t a single state with named fields anymore). And you might want to avoid doing it anyway.

The act function

And then there is the ReactTestUtils.act() API. There is a recommendation in the release notes to

wrap any code rendering and triggering updates to your components into act() calls

However, the examples above work fine without it and actually break when such a wrapper is added. I’ll be happy to hear your feedback on this.

More on Hooks

This article is the fourth and probably final part of my short series on React Hooks: