Testing a Custom React Hook

And gaining more confidence in your React Testing Library skills

Miroslav Pillár
Level Up Coding

--

Photo by Battlecreek Coffee Roasters on Unsplash

Recently, I wrote a story about How to Build a Custom React Hook for Fetching Data. The implementation of the useFetch is pretty straightforward, but it's a bit tricky to test properly.

We're going to use React Testing Library (RTL) with the assumption that you already have set up your testing environment.

Ok, let's just not talk in vain and jump straight to the code!

Creating the useFetch

Custom hook for fetching data

A custom hook is a special type of function, in which you can use React hooks.

So here we're fetching data from the API in the useEffect hook, putting them into state and returning. If an error occurs during fetch, we return the error.

But before anything else, with ref.current we're checking if the current reference to the component is true. If it's not, it means the component is not mounted to the view. Therefore fetching data should not happen.

Testing custom hook

Now, we can cover the whole functionality of the useFetch by testing whether:

  1. data are returned after a successful asynchronous call to the API
  2. an error is handled correctly when occurs
  3. call to the API is not happening when the current reference is falsy

For each point, we can write a separate test, right? So let's get started!

Setting up a testing file

Suppose we create a new file useFetch.test.js and prepare a testing environment:

  • We will definitely need mocked data since we're not going to really fetch them. Hence I created an array of stubbedCountries.
  • In afterEach block we clean up fetch implementations, whereas in afterAll we restore the original implementation. This way we prevent tests to affect each other. More about this in jest docs.

Case 1: Getting the data

Ok, now let's talk about this step by step:

  • As always, we firstly mock asynchronous call by returning resolved Promise with stubbedCountries.
  • Hooks are special types of functions because they can be used only within React components. Hence we need somehow to mock rendering hook in the component. For that, we can use renderHook().
  • A callback of renderHook() is an execution of the hook with proper arguments. You can read about more options of renderHook().
  • After that, we call waitForNextUpdate() which returns a Promise that resolves the next time the hook renders, commonly when a state is updated as the result of an asynchronous update.
  • We expect the result (returned object) should strictly equal to the desired output.

Case 2: Error handling

This is pretty similar to the previous test:

  • Again, we mock asynchronous call by returning Promise which must be with rejected status. It means something went wrong and the error should be caught.
  • Same as above, we render our hook and call waitForNextUpdate().
  • We expect the returned object should contain error and no data.

Case 3: Current reference is falsy

The React component which uses the useFetch was in the meantime unmounted from the DOM and therefore fetching should not be triggered:

  • Here we're just rendering the hook with given arguments. The second argument is ref, so it must be { current: false }.
  • In assertion, we expect that fetch has not been called. This means useEffect in the custom hook has not been called.
  • We also expect the returned object should not include data nor error.

Conclusion

In the end, this is what we were trying to achieve:

You can also check the test file in the GitHub repo.

However, what's important that we covered all possible cases with just 3 tests. This means, that coverage should be 100%, although I don't consider this aspect as relevant.

And the best thing is, that you can use this hook in every React project because the code is fully tested!

Want to learn more about testing? Feel free to read my other stories:

Unit testing with RTL

Automation (E2E) testing with Cypress

Thanks for reading!

--

--

A self-studied web developer with a passion for writing about Frontend, Javascript and its related content.