Testing Custom React Hooks with RenderHook and Act
00:00 All right, let's open up our UseDoubleCheckTest. And here, we're going to get our result from Await RenderHook from Testing Library React. And we're going to call UseDoubleCheck from our utilities.
00:16 So this result is going to have a current property on it. And you might say, well, Kent, why doesn't it just return exactly what this UseDoubleCheck returns? Why do we have to have this current intermediary? And the reason is because reference.
00:32 So we need, or RenderHook needs to have some object that it can update all the properties on it. And that way, we can always have access to the current value of that. If we didn't have this current, then our results would only ever be able to have the previous values. And as we're making state changes,
00:51 it's not going to get the latest version of the double check value. And so you always, always have to reference everything that is returned by your hook off of the current value. Otherwise, you'll end up having stale values. So there you go. There's a little tip for you.
01:10 Speaking of the double check, let's expect that to be false from the outset. We'll bring that in from vTest. And save that. Let's make sure that our test is passing. Very much is. And make sure that it can fail. So we'll set this to true. And it does indeed fail. So great.
01:30 Set that back to false. And come on back over to our tests. OK, great. So next thing we want to do is we want to call the onClick so that we can test that this double check is set to true. So I'm going to create a mock function, because we also want to make sure
01:50 that if we provide an onClick function, that we end up calling that. So that's what the call all is supposed to do, is it will call the onClick. And then it will call our provided onClick. And the tricky thing here is if we want to call this onClick,
02:04 it's expecting a button event for a click event on a button. And unfortunately, that's a little bit difficult to construct. So we're going to click this multiple times. So I'm going to call this click1. And we'll make a new mouse event. This is a click event.
02:22 And for this to work properly with React, we need to set bubbles and cancelable to true. And then the type is not specific enough. So if we were to say result current getButtonProps onClick with click1, the TypeScript's not jazzed enough about this.
02:41 So we have to do this nonsense as a known to just set it to anything. And then as React mouse event HTML button element. Boo. I don't like it either. So you can imagine why I might prefer the next thing we're going to do.
03:00 So let's continue with that now to make an assertion. Well, here we can just save that and make sure that nothing's blowing up. So that's good. Now we can make an assertion that this should now be true. And we save that. And kablooey. It's not true. And here is the problem.
03:19 When we say, hey, getButtonProps, I want to call your click method. So we go into that click method. And yeah, there's the call all utility. Eventually, we'll end up in here. And if we had a console.log, console.log, hello, save that. And we do, in fact, get that console log. So what gives?
03:39 In fact, we could also, another thing we want to assert on is that our click handler. Oh, yeah, we also want to provide a click handler. Let's do that really quick. So onClick, myClick, and we'll say myClick is a VI function.
03:57 So just a function that keeps track of when it's called and all that. OK, so now we should be able to expect myClick to have been called times once and only once and expect the default to be prevented. So I'll stick those before this assertion.
04:16 And those assertions do work. So we definitely are calling the onClick function. It's just for some reason, when we say setDoubleCheck, that doesn't actually get set to true. Well, here's the rub. Here's the problem. React is not necessarily synchronous. And so what it does to improve performance
04:36 is you can call setState all over the place, all these stateUpdater functions. You can call them all over. And it will just kind of collect them. And it won't actually make the changes and call re-renders until it's ready. And it'll say, OK, so you've done all your stuff. Let me look at all the things that you did and execute all those things.
04:54 And then I'll run the useEffects as well. And so the problem with what we have now is we're not giving React a chance to say, OK, I guess you're done, and I will render everything. And so what we have to do is we have to say, OK, I want to set up the start and stop for your batch.
05:12 And when this callback function is done, then before you proceed, I want you to run all of your setStates and your re-renders and all that stuff. And then I can proceed on the new output. So that's why we have to use act.
05:28 So we're going to await act from testing library React. And that will be that. That's it. Now it's wrapped in act, and our test will pass because React knows, oh, OK, so after you're done with whatever this callback does,
05:45 I can go ahead and flush all the state updates and re-render everything and call useEffects. And so by the time we continue on, once this act is resolved, we can be rest assured that the state of our state and our DOM is correct.
06:05 OK, so that's act. Act is definitely no fun. And most of the time, you actually don't have to do this because most of the time, testing library will do this automatically. In fact, the render hook, that is calling act under the hood. The render function from testing library, also calling act under the hood.
06:24 Anytime you call fireEvent to fire events, we're going to look into events later in the next step. Or you're calling userEvents, like we're going to do in the next step. Those are all going to be wrapped in act automatically. So the only time you ever have to call act manually like this
06:41 is if you are manually triggering a set state or a state updater function at some point, which is exactly what we're doing here. So that's why we have to do that. OK, great. So now, once we've verified that clicking it once is going to update the double check,
06:59 let's clear our mock so that we can do this again. And we'll stick that right here. And so now, we're going to make another quick event. You can't reuse the same one because default has already been prevented, and that's not how it works in the browser and all that. So we're going to make another click. We're going to do another one of these act things. In fact, you know what?
07:17 Let's just grab all this stuff right here, and we'll stick it right there. And the default prevented should be false in this case. And yeah, there we go. And then we make sure we pass click two. And the most important part here is that the default prevented is false
07:36 so that the default behavior can be applied. So we save that, and it blows up because default was prevented. All right, let's look at why that might be. Oh, and it's false because this says click one. No, we want click two. That's what you get for copy pasting stuff. OK, great.
07:54 So now, that is working swimmingly. And you know, another thing that would probably be a good idea is to make sure that our my click is called with the right event as well. So two have been called with, there we go, click one. And this one should be called with click two.
08:11 And now, we're pretty confident that our functionality works as designed. So the big takeaways here, the render hook is how you can render a hook. And whatever is returned by that will be on the result.current, which is the only way you access any of these properties. If you try destructuring this, like if we
08:48 But I mostly feel positive about that. OK, and then we create our own quick handler. We create our own quick event. And then we call this inside of act, so signaling to React that once this function is done, they can flush all their changes. And we can proceed with our tests and know that things are in a good place.
09:07 Then we clear out the my quick mock so that we can make another assertion that it's called only once. And then it's called with the right stuff. And we pretty much repeat the same stuff that we did before. And that is writing a test for a custom React hook.