Check out a free preview of the full Intermediate React, v3 course:
The "Testing Custom Hooks" Lesson is part of the full, Intermediate React, v3 course featured in this preview video. Here's what you'd learn in this lesson:

Brian live codes tests for the application's useBreedList custom hook by implementing a fake component to bypass the issue of custom hooks not being able to be called outside of a component. How to use the react-hooks library to automatically create fake hooks with renderHook and enabling mock fetch requests with Jest are also covered in this segment.

Get Unlimited Access Now

Transcript from the "Testing Custom Hooks" Lesson

[00:00:00]
>> Let's test some custom hooks. So I'm gonna create a new file here and call this use inside of tests useBreedList.test.js. Okay, here we're going to, so we wrote a custom hook in the complete intro to React that requests a breed list based on what an animal was.

[00:00:30] And it gets that from an API. So one of the things that's interesting about this is it makes an API request that we're gonna have to somehow all fake out, or mock as it were. So let's start writing our test first. We're gonna import expect and test from @jest/globals.

[00:00:59] We're going to import render from @testing-library, and we're going to import useBreedList from ../useBreedList. Now, a couple things here. We're trying to test a hook, right? And hooks always exist in components, so you can't test them outside of components. What do we do about that? Well, we can actually get kinda clever about it.

[00:01:34] We can write a function here called getBreedList that takes in an animal. And then we can write a basically a fake component inside of function testComponent. Cuz remember anything that returns markup, or is a React node compatible thing is a React node, right? So I'm gonna say list = use breedList (list) and have this return null, cuz remember, null is something valid that you can render in React.

[00:02:06] So now this testComponent, this needs to be animal, testComponent, technically a valid React component, right? And I'm just gonna grab that list here and then save it outside of that. Here, I'm going to render my testComponent. And then here I'm going to return list. So I wrote a helper function that's actually going to go create a React component, render it, and get the list out of there, using all of React's life cycles.

[00:02:42] It's kinda strange, I know, but it does work. So let's try that. Let's make this smaller so you can see more what's going on. All right, so we're gonna say test. Gives an empty list with no animal, right? Because we want it to always give some sort of array back.

[00:03:12] AlI right, this empty array is more accurate. Okay, async function we're gonna say const BreedList status = getBreedList. Okay and then I'm gonna say expect(BreedList) to have length 0 and expect(status) to be unloaded, right? Because if you haven't done anything with it, if you haven't actually reached out to the API yet, it's gonna be unloaded.

[00:03:55] It's gonna be uninitialized basically. And there you go, that passes. So that this works okay because we didn't we're not actually hitting the API, right? We're actually hitting fetch, right,because we don't give it an animal it doesn't actually reach out to the API. Now we can actually make this a little bit easier.

[00:04:20] There's a thing called testing library/react hooks, so they don't have to do all this black magic here. It'll do the black magic for you, it's basically doing this. But let's go ahead and install it, so I'm gonna, oops. If I hit q here, it'll actually stop my test suite from running.

[00:04:36] And then I can say npm install -D @testing-library testing-library. And I wanna install react-hooks@5.1.0, Okay, cool, we're put Jest back in watch mode. And then I'm gonna come up here and underneath this I'm gonna say import, renderHook from @testing-library/react-hooks. This is basically gonna do this for us, and it's just gonna make this look a little bit nicer.

[00:05:31] So instead of doing that getBreedList stuff, cuz I hate writing a bunch of logic to test something else cuz I feel like I'm re-implementing a bunch of stuff. And I feel like I need tests from my test at that point, right? So instead I'm going to say render-hook, and it's just a function that takes in useBreedList of whatever I wanna get.

[00:06:04] Undefined is not a function. Yeah, here that's fine. Okay, and then I need to say, const[BreedList, status] = result like that. Getting closer, what is result? Result is not iterable. Let's see what did we do here.
>> .current on result.
>> .current on result, thank you. So results gives you back some other things besides just the results, right, the current result.

[00:07:07] So you do have to pull the current one out of there, yep, thank you. Cool, and now we have a passing test. This is much easier to read than that function test component business that we were doing. This is a really nice helper to help you do that.

[00:07:25] All right, so that's testing a hook. Now we need to go mock that API so that if we give it something like dog here that it's not actually gonna request against my API, right? It's actually going to go and do some sort of mocking result there. AIl right, so let's go in and do that.

[00:07:46] Let's go ahead and quit out of our watcher here. We're gonna do npm install -D, jest fetch mock@3.0.3. This is a tool that's actually gonna help us just mock fetch. So mock is a term that basically says, here's a fake implementation of that. So what it's gonna do is it's going to replace the browser fetch that it expects to have.

[00:08:13] And it's gonna replace that with a implementation that we're going to provide to it so that we can provide it with fake data. So it never has to hit our API. It's a good idea, your tests run all the time. APIs are slow, you don't wanna hammer your API, just a better for everyone if you mock that.

[00:08:32] Okay, run my tests again, maybe it actually we'll wait a second for that. So in your package.json, just down here at the bottom, put another top level thing called jest. This is like configuration for jest, 1automock false. We don't wanna it to, you can have it say like mock everything by default.

[00:08:57] That's what it used to do, it was terribly annoying. And we're gonna have a setup files. And one of them that we're gonna have a run is ./src/setupjest.js. So we're gonna have it run this before it runs the testing suite. And then we can enable some of those mocks.

[00:09:19] So we just added this, the jest automock false. Okay, we're gonna create a new file here called setupjest.js that's gonna go in the source directory. Not the test directory cuz we don't want jest to think of that's a test cuz it's not. And really short here, we're gonna import enableFetchMocks from jest-fetch-mock.

[00:09:51] And then we're just gonna run that enableFetchMocks, that's it. Now, here you can actually go in through and make an entire mock API. I kind of like doing it closer to the test. In the test, here's what this particular test is expecting so I can see everything in one test.

[00:10:08] Personal preference, there's not a wrong or right answer there. Okay, so now we have that, let's go back to our useBreedList. We're gonna make a new test down here at the bottom. That's what we're testing, useBreedListTest, I want to say test gives back breeds with an animal. It's an async function.

[00:10:42] I wanna say const breeds = and just give it like a list of some breeds like Havanese, Bichon frise, Poodle, and I don't know, Corgi or something like that. Then I'm gonna say fetch.mockResponseOnce. So the next time that jest fetch gets called respond with this and I'm just gonna say json.stringify, Animal, dog, and breeds.

[00:11:29] So this is a fetch response that this particular thing would expect to see. And then what I can do here is I can say, const result waitForNextUpdate = renderHook. UseBreedList with dog. So what this waitForNextUpdate is gonna do is like, okay, something's gonna happen and then you gotta wait for this to update and then we can test it.

[00:12:08] So it's gonna give you back basically a promise of you gotta wait something async is gonna happen. So I'm gonna say await waitForNextUpdate like this. This is going to say, okay, I'm gonna get something back, wait for something to happen. Then we can continue the test after this point and now I can say const BreedList status = result.current.

[00:12:40] I can say expect(status) toBe(loaded), right? Because it's gone to the API and come back. And I expect BreedList = breeds, right? Whatever I gave it here to respond with, that's what I expect to get back here. All right, save that, now we're gonna say npm run jest or test watch.

[00:13:21] And you can see here, all of my tests pass. So again, I always like to make sure that things fail when I expect them to. So let's say I go into useBreedList and I'm going to say that setBreedList here is gonna just be, And you can see here, now it fails.

[00:13:51] This is like, hey, I expected to get these back and you gave me back lol hi, good job. So that's good, and then what happens if I say nothing here? Again, I expected loaded and I got nothing, right?