This video is available to students only

Test our Custom Hooks

Testing a custom hook might sound intimidating at first, but there are actually some repeatable steps that take a lot of the guesswork and mystery out of it.

Test Hardware Handler's custom hooks#

Integration testing React Custom Hooks is a little bit different than testing functional components because these hooks don't have to be tied to a specific component. But not to worry, React Testing Library's @testing-library/react-hooks package will help us handle them.

We'll go through the hows and whys of testing custom hooks in our React application in this lesson, so you'll be ready to tackle any hooks you may come across.

The useProducts.js Hook is a good hook to start with#

Since our custom hooks aren't tied tightly to a particular component (the whole point of them is decoupled, reusable functionality), and they live in their own separate hooks/ folder inside of our app, to test them, I like to create a test/ folder to group all the hook tests together.

Make a new test folder and file for our custom hooks#

To get us going, let's make a new test/ folder inside of the hooks/ folder and create our first test file.

Keep it explicit about exactly which custom hook is being tested by naming the new file something like: useProducts.test.js.

From here, we can get started on our tests for the useProducts Hook.

Set up our test file#

Unlike our other test lesson, which used the @testing-library/react library for methods like render and fireEvent, this custom hook is going to use the @testing-library/react-hooks library.

Just like the first testing library, this hooks library aims to provide a testing experience as close as possible to natively using a custom hook from within a real component.

Define a render hook helper function

The key method we need from this hooks library is called renderHook.

To make use of it, we'll import it at the top of our file, then create a reusable helper function to render our hook without having to copy all the required boilerplate for each of our tests.

Okay, let's talk about what's happening up above in this renderUseProductsHook function.

When we're rendering a function component with JSX, we can just pass any props and mock any data that component needs. For custom hooks, however, we have to create and render the hook to test and mock any variables or functions being passed into the hook (there are none in the case of the useProducts Hook).

Once that's done, we'll call the renderHook method we imported from the testing library and create an instance of the hook to test (useProducts) inside of that method. It's in here that we'd pass any previously defined mocks or variables required by the hook.

From the renderHook function, we destructure two objects: the result and the waitForNextUpdate function.

waitForNextUpdate is a function that returns a Promise that resolves the next time the hook renders. We need this function to run after the first renderHook function to wait for React to update. This is because React Testing Library can't wait for the DOM to update because there is no DOM when testing custom hooks.

Finally, the result and the waitForNextUpdate variables are returned from the function because we need them for our actual tests.

By defining all of this within a helper function, we can call this function from all of our tests and save ourselves the duplicate code to make these hooks.

Time to move on: let's put this function into practice.

Mock the product API

Another piece of setup we need to do for this hook test is mocking the productApi function that the hook calls: the getAllProducts function.

Since our hook will call this function every time it's run, it makes sense to wrap our data in a beforeEach function.

First, we'll write our generic describe block for these tests to live within:

Then we'll set up our mock where we spy on the productApi and the data we expect to be returned from the mock.

And if we set up a beforeEach, we need to set up an afterEach to clean up and reset our mocks for each test.

I think now we can get to testing our hook — finally!

Test that products and brand filters return from the hook

Our hook returns three separate pieces of info, but two of the pieces — the products and the filters by brand name — are tied together. As long as the product API returns data, both of those pieces of information can be determined from it. So it makes sense to me to test for both of those pieces of info together.

Here's how we'll approach these tests: we'll render our custom hook using the helper function we defined and destructure the result object from it.

From the result object, we'll look for the products and filtersByBrand properties and compare their values against our mocked data set that we composed in our previous lesson in the mockDataSet.json file.

Not only will we check the length of both variables to ensure they match our mocked data, but we'll also use the Jest method toStrictEqual to test that the objects have the same values and structure.

If we want to, we can even check that the error still has the value of false in this test.

Here's what my test looks like.

As you can see, once the result object is rendered from our custom hook, making checks against the data returned is pretty easy and similar to things we might check in a typical functional component.

Ready to move on to when our productApi call throws an error?

Test if the product API fails, the hook returns an error

For this test, we'll override our expected getAllProductsMock with a custom one that throws the error from the server instead of returning data.

Once more, we'll then render the hook, extract the objects from the result.current object, and check the values are what we expect when this getAllProducts API call fails.

Here's how I'd write this second test.

That looks pretty sensible, right?

Run code coverage for our new hook test#

As a gut check, let's run our new test file and check the code coverage for this custom hook now.

Here's the terminal's code coverage output.

Start a new discussion. All notification go to the author.