How to Test Custom React Hooks With Testing Library
This lesson preview is part of the Fullstack React with TypeScript Masterclass course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Fullstack React with TypeScript Masterclass with a single-time purchase.

[00:00 - 00:13] Testing the use products hook. To test the hooks, we'll have to install the testing library react hooks package. From the root of the project, run the following command, yarn, app-dash, dev, add testing library react hooks.
[00:14 - 00:31] Here I'm going to be using a specific version 5, 1, 2. And again, we'll have to use ignore engines because of the problems with the CSS package that we're using. There's dash, ignore engines, and then press enter. Our use products hook does a bunch of things.
[00:32 - 00:45] First of all, when it mounts, it features the products. While the data is loading, it returns loading crew. If loading fails, it returns an error, and if data is loaded, then it returns a list of categories with their products.
[00:46 - 01:03] Inside of the home directory, create a new file, use products spec.ts and plan the test that we're going to write. We describe the use products. First of all, we want to test that it features the products on mount, features, products on mount.
[01:04 - 01:24] Then we have a bunch of cases, while waiting API response, while loading with error response, and with successful response. Define the callbacks. Now, while we're waiting for the API response, while we 're loading, it returns correct loading state data.
[01:25 - 01:37] Or that's what we want to test. With the error response, it returns error state data. And with the successful response, it returns successful state data.
[01:38 - 01:54] The use products hook depends on the API module. We want to test this hook in isolation. We don't actually want to perform network requests, so let's mock the API. Just mock utils API. Here we want to mock the get products function.
[01:55 - 02:12] Mock it with just a fan. Adjust the types, const get products. Mock equals get products, as unknown, as just mock. And then here we pass partial, return type, type of get products.
[02:13 - 02:22] Impert the get products function from the utils API. Let's write the first test . We want to verify that it's going to fetch the products on mount.
[02:23 - 02:41] await, act. Don't forget to make the callback async. We do it inside of the async act, because use effect will perform a network request. And as it is going to update the state of our component, after we already rendered the initial state, we need to make the test asynchronous as well.
[02:42 - 02:52] So we kind of wait for the next tick, at least. So we do an await act, async function. It is important to import the act from the correct library here.
[02:53 - 03:05] As we're testing a hook, you want to import act from testing library react hooks. If you import it from the regular react testing library, you might get some errors.
[03:06 - 03:19] Now inside of this async function, we render hook, use products. Impert the use products on the render hook function. Now we expect get products to have been called.
[03:20 - 03:33] So here we test that after the hook is mounted, it performs a call to the API. It is convenient that we've put the get products into a separate API module, because now we can mock it and then see if it actually got called during the mounting phase of our hook.
[03:34 - 03:48] Now let's test the waiting state. Remove the to do, pass in a callback. Here we'll want to mock the return value of our get products mock, get products mock mock return value, pass in a new promise.
[03:49 - 04:00] That will just never resolve. That's the idea. It will always be in waiting state. Under the hook, const result equals render hook use products.
[04:01 - 04:10] Expect that result current is loading to equal true. First thing that we test is that is loading flag is true.
[04:11 - 04:25] Then we want to verify that error is false and also the categories should equal an empty array. Here the most important part is that we mock the return value of the get products mock as a promise that will never resolve.
[04:26 - 04:39] We make sure to make it always pending by not calling neither resolve nor reject on the callback that we passed to this promise. Now let's test the error response. Remove the to do, pass a callback.
[04:40 - 04:56] Here we also want to mock the return value of the get products mock, but instead of a promise that never resolves, we want to reject it. Resolve, reject and then inside of the body of this callback, we immediately call reject and we pass in error text.
[04:57 - 05:13] After it's done, we render our hook. We want to get result and wait for next update. Again, for the same reason as in the first test, because our hook is asynchronous, we want to wait for the next update before we test for the values .
[05:14 - 05:27] Render the hook, use products, wait for the next update, wait, act. We use act because internally inside of the hook, it will update the error state and then wait for next update.
[05:28 - 05:34] Make sure that your callback is asynchronous and now write the expectations. We can copy them from the previous test and adjust them.
[05:35 - 05:50] We want to make sure that this loading is false, error is error and the categories are an empty array. In this test, the data fetching happens inside of the sync function in our hook and as a result it updates the state.
[05:51 - 06:06] To handle it correctly, we used act to wait for the next update before we can test for our expectations. Now finally, let's test the happy path, remove the todo, pass in a callback, define the mock return value, here we want to resolve the promise.
[06:07 - 06:18] Let's format the document. Instead of rejecting it with an error, we resolve it with an object with the categories. That is going to be an array with one object with name, category.
[06:19 - 06:30] We don't really care about its contents, so the items is going to be an empty array. Now let's render our hook, const result, wait for next update.
[06:31 - 06:38] I think you already guessed why do we need to wait for our next update function here? Render hook, use products.
[06:39 - 06:53] And that's because same to the previous test, the state update is going to happen asynchronously after we fetch the data. So we need both act to wrap the state update and also wait for the next update because the function was asynchronous.
[06:54 - 07:01] Wait, act, wait for next update. Our callback should be async because of it.
[07:02 - 07:10] And after the update, we can perform the expectations. We can copy them from the previous one, the only one that we're going to change , or actually a couple.
[07:11 - 07:25] So the error should be false, but the categories should now contain a category with name, category, and items, empty array. Let's wait the test is passing, and we've just tested our use products hook.