You don’t need to interact with your components to write isolated tests for hooks

Federico Alecci
May 23 · 5 min read

If you want to skip it all the intro and explanation, feel free go to the bottom or just visit the repo. However, if you want to leave with a better understanding, enjoy reading the full story.

So everybody has hooked up (see what I did there) with React hooks and other new React features by now. If you haven’t gotten the chance to play around with them, you can start by giving a read to the documentation. As always, it has been really helpful.

Developing components with hooks has been a lot of fun for us. We’ve strongly come to believe they help to create components that are much easier to read and are more meaningful. Nonetheless, as we were going down the road, we got to the point where we had to start writing some tests (yeah, as this was our first real experience with hooks, we were really eager to ignore TDD for this time).

Although there were really helpful posts of testing React custom hooks, most of them involved interacting with a component, rather than testing hooks itself. While in many cases, this would be fine to test your custom hook, there are certain times when that’s not a happy path to get through (for instance, if you need a lot of UI interactions in your component).


Let’s Go to the Code!

Before we get started, you should be aware of a few things:

We will be using this lovely useKeyPress hook for demonstration. Although pretty much self-explanatory, this hook returns an object with:

  • pressedKeys: an array of pressed keys.
  • setPressedKey: a function to add a key to pressedKeys.
  • removeDuplicateKeys: a function to remove duplicates in pressedKeys.
  • clearPressedKeys: a function that clears our pressed keys.

We have a simple, silly looking component for messing around with this stuff:

Might call it Silly.jsx

And below, you will find its code. We are using useEffect to add a window event handler every time someone presses a key.


So, here we are. Staring at our favorite editor. Blank file. Looking as if we were ready to create the next unicorn. How hard could it be? After all, custom hooks are plain JS. Our hook returned some values and functions. We already had Jest on our project. Let’s get started.

One of the first things we tried to do, was to invoke the hook directly in our test like this.

Are you able to figure out what the result of the test would be? If you are like most of us, you ran it and encountered this error:

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 Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

Shame on us! That’s pretty clear 😢. You can’t use a hook outside a hook function or a React component. So, what are our options? We could either create a wrapper component (that would have another catch) or we could use react-hooks-testing-library package to make our lives easier.

OK, what are we doing here? Basically, we are importing renderHook from react-hooks-testing-library; a wrapper function that will help us (perfectly named btw) “rendering” our hook.

After that, instead of invoking useCustomHook directly, we will pass it to renderHook as a parameter. renderHook will return an object, which we’ll destruct into result.

Finally, we have a current object inside result that holds our hook returned object, hence we can easily assert pressedKeys value.

Now that we have our initial setup going on, let’s play around a bit with our hook. I’m planning to call setPressedKey to add some values to pressedKeys and check if everything is in place.

We will run these tests as if we were the kings of the hooks, but although it hasn’t failed, we got a pretty nasty warning.

Warning: An update to TestHook inside a test was not wrapped in act(…).

When testing, code that causes React state updates should be wrapped into act(…):

act(() => {
/* fire events that update state */
});
/* assert on the output */

Okay, fair enough. To wrap our call to setPressedKey in this act method, we just need to add it to the import of react-hooks-testing-library.

With these, we should be ready to cover all the possible scenarios for our hook. This is our final test file.

We were able to test everything we needed from our hook and have event tested to make certain we get the correct object with our functions, instead of just checking the initial value for pressedKeys.

Finally, here is the link to the full repository if you want to get a working example: https://github.com/falecci/plain-js-hooks-testing.

One last gotcha we encounter on our path is that you can’t destructure current from result, because this value changes every time we call our hook, therefore we would never get the updated values.


If you are based in Argentina and looking to work on a really challenging product, come and join us at Morean. We are working with React, Node and Python, on a serverless architecture on AWS, having tons of fun keeping up with the latest tech stacks and delivering a great product.

I hope you’ve liked the article and found it helpful. As I said in the intro, we are still walking our path with Hooks, so if you have some suggestions, please feel free to leave a comment!

Big shout out to my teammates Salvador Palmiciano and Gabriel Vazquez, for being awesome people to work with and giving me a hand with the drafts.

Better Programming

Advice for programmers.

Federico Alecci

Written by

Fullstack Dev at Morean.co. Having fun with React ecosystem, and serverless architecture.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade