Check out a free preview of the full Complete Intro to React, v9 course
The "Mocking" Lesson is part of the full, Complete Intro to React, v9 course featured in this preview video. Here's what you'd learn in this lesson:
Brian uses a mock to test the API call. Mocks are helpful since they can emulate difficult-to-test scenarios or avoid unnecessary load on an API. In this case, the test ensures the contact form is submitted with the correct data.
Transcript from the "Mocking" Lesson
[00:00:00]
>> Brian Holt: So we have API requests in a bunch of our various different components. You have two choices here, and I'm gonna say you actually really only have one choice. But in theory, you could spin up your API and you could have it make real requests to a fake API or something like that.
[00:00:16]
Don't do that, it usually ends in heartache. [LAUGH] so in that case, you probably wanna mock it, make mock requests so that it goes to make a request and then it just gets faked an answer back to it. That's what I'm gonna teach you how to do now.
[00:00:32]
So we're gonna npm install vitest-fetch-mock@0.3.0. We didn't have to use vitest-fetch-mock, it's actually really easy to do directly with vitest. It just adds some niceties and I just didn't wanna make you code all that stuff, so we're just gonna stick with it. Let's write the code for testing,
>> Brian Holt: We're gonna test the contact page.
[00:01:01]
So let's go create another test file in here, and this one gets to be called something that's kind of silly, it's contact.lazy.test.jsx.
>> Brian Holt: And if you're looking at it as like, that is the dumbest name of a file that I've ever created, just wait, we're about to do one that's even sillier, but why?
[00:01:29]
Well, lazy, it's being dependent on that for React or a TanStack router. And test, it's being dependent on that for the testing suite. So this is why I kinda get a little hesitant about should file names be significant? Cuz then you end up with silly stuff like this, which obviously this is silly, but here we are, we're gonna use it anyway.
[00:01:53]
So import render from @testing-library/react, import, expect, test, and another one here called vi that we're gonna talk about. I promise you, it is not Vim, it is is different than that. And in any case, I will show you how to get out of it afterwards. Import createFetchMock from vitest-fetch-mock, import QueryClientProvider and QueryClient from tanstackrouter, sorry, tanstackquery, rather QueryClient.
[00:02:44]
>> Brian Holt: And import Route from ../routes/contact.lazy.
>> Brian Holt: So why are we importing stuff from ReactQuery? That seems kinda strange, right? Well, it's because in order for ReactQuery to function, it depends on a QueryClient being there, and we're not rendering it in the context of app, right? An app is what actually possesses the QueryClient.
[00:03:14]
So we have to create a fake one for it. It's not a fake one, it's actually a real one, but we have to create one for it. So that's exactly what we're gonna do here. We're gonna say const QueryClient,
>> Brian Holt: = new QueryClient.
>> Brian Holt: Okay, let's talk about vi for a second.
[00:03:37]
Vi is the name of the spy library inside of vitest. So if you've ever used sinon in old React testing, I guess it's still current, you can still use sinon in lots of stuff, it's the same idea. But it's basically a thing that's like, all right, I'm gonna create a fake version of this, and then I'm gonna ask later, it's like, did this get called, what functions does this gets called with?
[00:04:01]
That's called spying in testing and that's what vi does for you. So we're gonna say const fetchMocker = createFetchMock(vi). And this is gonna kind of fetchMocker.enableMocks. What this does is it goes and monkey patches fetch to be now testable, right? It's not actually gonna go call really the API, it's just going to fake like it did, right?
[00:04:32]
And now what's cool about that is we can say, hey fetch, what were you called with? And that's a good thing for us because now we can go back and say, did you call the right API? Did you do the right things given that kinds of inputs, right?
[00:04:45]
That's the point. All right, so we're gonna say test can submit contact form. Seems like a pretty good test, right? Async function,
>> Brian Holt: And this one's gonna have a little bit more, but we're gonna do actually simulated test events, which is gonna be cool.
>> Brian Holt: All right, so first thing we're gonna say, fetchMocker, mockResponse once and not just mockResponse.
[00:05:26]
>> Brian Holt: I don't think it matters cuz we're only gonna call it once. In any case, basically you're saying to the fetchMocker is, hey, if you get called, respond with this from the API.
>> Brian Holt: Because we know only one API is gonna be called in the course of this, we don't have to really think too much about which one was called and why and that kind of stuff.
[00:05:48]
But I can imagine, let's say you have something that has nine different API calls, you have to get a bit more specific, all right? To this route, respond with this, to this route, respond with this. We don't have to care too much about that, so we're not going to.
[00:06:03]
>> Brian Holt: All right, and then this is gonna be just status ok cuz that's what the API responds with now. All right, we're gonna say const screen = render. And remember, we have to give it a QueryClientProvider. And a QueryClient = as you might guess, QueryClient.
>> Brian Holt: And we're gonna say Route.options.component.
[00:06:41]
So why this kind of strangeness? Well, if you remember from contact here, what gets exported is actually this lazy file route, which has a bunch of machinery around it that the TanStack router uses. We don't need that, we just need the component. So that's why we're doing the Route.options.component, that's where that comes from.
[00:07:09]
All right now let's simulate all of our input events. So we're gonna say const nameInput = screen. And when you can do getByPlaceholder, I was like, that seems like actually a pretty appropriate one, because that's something that the user would experience, right? And we'll do three of those.
[00:07:30]
And we'll do emailInput, email.
>> Brian Holt: And it msgTextArea,
>> Brian Holt: And it is Message. All right, so now we have handles on all of our various different inputs, and we're going to put some test data together. Const testData = put whatever you want in here, I'll just put Brian, email, dustinshatemamail@example.com.
[00:08:19]
And message just let Brian teach, Dustin. So I'm gonna say nameInput.value is assigned testData.name. And again, we're gonna do that three times. The emailInput is gonna be email, and the msgTextArea is going to be message. Again, this would just be like a user filling out a form, right?
[00:08:53]
All right, after they fill out the form, what do they do next? Click the button. Const btn = screen.getBy what, role, getByRole. Button's a pretty good role, there's only one button on the page as we know. What are you gonna do with a button? Click,
>> Brian Holt: Kind of a cool feature, right?
[00:09:27]
>> Brian Holt: Now, this one's kind of cool, I like this. Let's just go do it on our page and see what happens.
>> Brian Holt: Okay, how do we know that our page was submitted correctly?
>> Brian Holt: What did we do wrong here?
>> Speaker 2: Your server's not running.
>> Brian Holt: Is it not?
[00:09:52]
It's not, you're right.
>> Brian Holt: I'm not gonna get it running right now, but what would eventually happen is the submitted shows up, right? That's what you would expect to show up here. But it's not instant, right? It goes to the loading state, and then eventually it settles in an h3 of saying submitted.
[00:10:14]
But we know that the entire process went through once that h3 shows up. So what you can say is you can say const h3 = await screen.findByRole. We're looking for a heading of level three which is another way of saying level three or h3, right?
>> Brian Holt: This await means hey, this isn't gonna show up right away, but wait for this.
[00:10:49]
Wait for it to happen. Once this shows up and it just pulls it, right? So is it there now, is it there now? And once it's there it's to be like, okay, cool, here you go and here is your h3. And then if it never finds it, it's gonna error out because it says, well, you wanted this and it never showed up, right?
[00:11:08]
Now we can just say, expect (h3.innerText).toContain("Submitted"). toContain is nice here rather than toBe. If you remember I think it actually is submitted with an explanation point. I just care that it that shows them that it was submitted, right? So that's why I'm gonna do contain instead of toBe.
[00:11:37]
Some people might argue you're good, you did it, you can stop here. I wanna go further and I wanna say did they call the API with the right thing? I think that's user facing, because if it doesn't call the API with the right thing, the user's data is getting lost, right?
[00:11:55]
There are people that would argue that testing the API endpoint and the data is implementation details and shouldn't be tested. And I mean, they're wrong, and that's okay, right? Not everyone gets to be right all the time, except me, because I'm teaching. So we're gonna test that, requests = fetchMocker.requests and this is gonna give you back everything that it got.
[00:12:27]
So the first thing I'm gonna say, if you got more than one call, what happened? expect(requests.length).toBe(1),
>> Brian Holt: And then I'm gonna make sure that it called the right thing, (requests[0], it's the first thing, right, .url).toBe, to be make sure that it called the right one, ("/api/contact");. expect(fetchMocker) and then you say .toHaveBeenCalledWith("/api/contact"), and then you give it what you expected to have been called with.
[00:13:17]
So it's going to be body: JSON.stringify. This one I'm gonna say, this is getting dangerously close to implementation details. But headers: Content-Type": "application/json" and method: "POST".
>> Brian Holt: Nope, don't do that, "POST".
>> Brian Holt: So this is basically saying, I expect when they called fetch they called it with this, the correct URL and the right testData, headers to be correct method: "POST", all that kind of stuff.
[00:14:07]
So again, let's go ahead and say npm test and see what happens. Failed the test, let's go see what happened. So useMutation, no QueryClient set, did I? I thought I did.
>> Brian Holt: It's client, not QueryClient. So bear with me a second, come back up here. I said QueryClient = QueryClient, right there.
[00:14:33]
This is actually just supposed to be client.
>> Brian Holt: This is something that if we had TypeScript, it would have yelled at us right away. So that is one of the nice things about TypeScript. Yeah, TypeScript will immediately say when you don't pass in the right props that it expects.
[00:14:54]
It's one of the nicest things about writing React with TypeScript. Okay, nameInput = screen. getAll, I don't want getAll, I want getByPlaceholderText.
>> Brian Holt: getByPlaceholderText. I'm surprised that actually works,
>> Brian Holt: Cuz otherwise it will get all of them and it gives you an array of them.
>> Brian Holt: But I guess you can set .value on an array, right?
[00:15:29]
It's not gonna yell about that. It's just weird. But let's see, did that, fix it? It did fix it, okay.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops