Check out a free preview of the full Web App Testing & Tools course

The "Mocking External Dependencies" Lesson is part of the full, Web App Testing & Tools course featured in this preview video. Here's what you'd learn in this lesson:

Miško demonstrates how to mock external dependencies with Vitest. Creating a mock allows developers to test APIs without wasting resources or waiting for server responses. For a mock to work, components should be written in a way that allows them to be tested more easily. The final code is located on the lesson-2 branch

Preview
Close

Transcript from the "Mocking External Dependencies" Lesson

[00:00:00]
>> So remember, I kind of alluded to the fact that it's all about managing your dependencies, right? And what I meant by this is that if you go to our API, this fetch call right here is really reaching out to an external world. And therefore, we are dependent on the external world and there's a couple of problems with it.

[00:00:23]
First of all, it is going to be slow. Second of all, it could be expensive. So for example, imagine you are trying to write software to process credit card information. You don't actually wanna make real charges as part of your test, right? Even if you are charging 0.01 cent, if you run the test thousands of times it can add up.

[00:00:51]
So you need to mark your external dependencies. And so what testing is really about is, how do I reach through to the other side of the unit that I'm testing and be able to insert friendlies on the other side. I don't know what you wanna call it. Basically insert something on the other side of this test that is my collaborator, that can basically sandwich the code that I'm interested in.

[00:01:20]
On one side, it is my test applying stimulus on one side of the of the unit. And on the other side of the unit is my friendly collaborator that is gonna pretend to do stuff so that I can sandwich the unit test in the middle and assert what I want about how the stuff works in the middle.

[00:01:45]
Does that make sense?
>> Why do you pass expect that in the argument of them?
>> Why is the expect passed in here? You can import it over here as well. But it turns out when you have an asynchronous test such as here And you want to run tests concurrently, then it would not be possible for the Vite to know the expect with which test it goes.

[00:02:14]
If you run the test one after the other, it's not a problem. But at some point, you probably wanna do concurrent testing. And once you do concurrent testing and you call expect and let's say this expect is doing to match snapshot. How in the world is supposed to know which test are you matching against, right?

[00:02:33]
And so a better way of doing it is to pass expect in through here and this expect is permanently tied. It's the same exact syntax, but it's permanently tied to this particular test run.
>> I see, it is snapshot here, how is it generated?
>> So the first time you run, the snapshot, see there's a snapshot folder.

[00:02:57]
So here, let's go back here. There's my test, and right next to the test, there's a snapshots folder and inside of the folder, there's a file name with the same exact thing. And the first time I ran it, it generated the snapshot, right? So for example, if I go here and I say false and I rerun my spec, now it's going to fail.

[00:03:19]
And it basically says like, hey, the snapshot is different. So one thing you could do is, at the beginning, we don't have a snapshot, so I'm just gonna delete the snapshot. The first time I run the test, it goes and it creates the snapshot, right? So that's how snapshotting works.

[00:03:39]
Snapshotting can be useful, but as I said, it's a thing that people naturally think of as the first thing to do, but it's extremely problematic for all the reasons that we mentioned. So the thing to mock out or collaborate is we need to get a control of the fetch.

[00:04:02]
And as it stands right now, there is no way for us to get a control of the fetch. I mean, we could monkey patch the global fetch and do all kinds of crazy things. Not the best way to do it, right? So what we wanna do is we wanna change this particular thing.

[00:04:16]
And this is where the magic of testing comes in, right? The magic of testing is how do I design my code in such a way to make it testable, right? It's not about the tools, it's not about knowing stuff about test, it's about designing your code in a friendly way.

[00:04:32]
So the way you would do that is you would start with like, let's say we wanted to mark fetch, is a new fetch, and you say VI. Sorry, vi.function. We have to import it and then we take this mock and we're gonna fill the details later and we're gonna pass it to the GitHub API.

[00:04:55]
So when I go to the GitHub API, we now need to get a hold of the fetch. So the type of it would be fetch and we can just add private keyword in front of it to save it. It doesn't know what fetches, so we're just gonna say that fetch is export fetch, is a type of fetch, type.

[00:05:21]
And so now we control the outside of the fetch and we come in here where we're doing a normal fetch and we're gonna go and fetch using the R fetch. The API is the same, everything looks the same, but now inside of our test we get to control this function.

[00:05:40]
Now if you wanted to add type information to it. So this function will have the parameters of fetch, a return type of fetch. Fetch has to be imported in here. So now we have a mock that represents a code that we send out. Now, if you look at the test, the test fails because the mock doesn't know what to respond with.

[00:06:12]
So if we look at our API, oops, that's not what I want, right here. If you look at our API, we call the fetch and the fetch is supposed to return a response and on this response we wanna be able to call the JSON, right? And so how do I do that?

[00:06:30]
So we need to train our mock so that it knows how to respond because currently the mock doesn't know how to respond. And because the fetch is asynchronous and returns a promise, a good way to train this is to be able to do a mock promise in here.

[00:06:44]
So if we had a function that would masquerade as a promise that we can control, then we could easily write these tests inside of our mock. So what we're gonna do is we're gonna try to create this mocking function. Wow, function, okay. So what we'll try to do is we're gonna create a promise, which is going to be a new promise.

[00:07:15]
And promises get resolve and reject and we will save them. So we're gonna say we're gonna have, sorry. Let resolve is a function that, so that's gonna be a Promise of T, and it's gonna take T, resolve. We're gonna have a reject. Now we're gonna assign it. Resolve is resolve, reject is reject.

[00:07:49]
And now we're gonna return a promise. But we need to assign those in there. So we need to do that and we need to do that. And now it's gonna be a Promise of T, but we also going to have additional things on it and these additional things are, we go like that.

[00:08:18]
Sorry, we need to have an exclamation, trust me we're doing this in the correct order. So now we have a mock, right? And so our fetch mock will return a promise, but we don't know how to resolve this particular promise. And this is the reason why our test is timing out, right?

[00:08:36]
Because the test is trying to do the next step of fetching from the output, but we can't do it because we haven't told it what to do. So let's train our mock a little bit. So once we have a fetch, what we want it to do is we want to assert, so I wanna say expect that the fetch mock to have been called with a particular parameters here.

[00:09:09]
So it is something like that. We're saying, we expect these headers and this output. Sorry, we have the wrong order. We need to call it first. Sorry, we need to call it and then we assert that we got called with this argument. And the test is still timing out.

[00:09:35]
But this is not right. Wait, this is not doing the correct thing. The correct way of calling it is that the URL is the first argument and then the headers is the second argument, right? So we need to flip this around. We have to say that we're calling it with this argument and then headers are like this.

[00:09:58]
Once we assert that we got called in the right way. What we can do now is we can resolve the expectations. So we can take the fetch mock, and we can say, the first thing that you returned, this was a promise, right? And now we can resolve this promise with the actual response.

[00:10:26]
And our response is going to be something that has a weight, so async JSON, which returns. And let's just return some string. Sure, response is good, response. What is it? Not liking, wrong number of parameters. This is a common problem that obviously even myself, who've done this before, has stepped into.

[00:11:04]
We are now awaiting, and the problem is that the await is in front of us having a response, right? So we need to get rid of this await, and this is response promise now, and then we respond and then we expect that if we await the response promise, then it is equal to response.

[00:11:29]
And now, it's passing, but expected R in JSON. Because this is not a JSON [LAUGH] There we go. So let's go over all of this stuff because there was a lot here. Let me see if I can fit the whole thing on the page. So we created a mocking function, a helper.

[00:11:59]
We were able to pass it to the GitHub API so that we sandwich the GitHub API on one side by us, the test runner, and on the other side by the mock. We then go and say we wanna do a request for this particular URL and we assert that the URL correctly got built up.

[00:12:27]
A couple of assertions we for example are doing here is, we're coming up with a username and a specific repository, and then we're verifying that the correct URL got built. We're also verifying that the correct headers got built up. And then we go and we create a bogus response, a mock response.

[00:12:48]
Notice, we don't have to pretend to have the full JSON, right? We're just saying this is the response and then we are expecting that the response actually comes over here. And so now this test is completely isolated, right? We no longer have to worry whether the fetch service actually works or not, and so on.

[00:13:10]
Now, it might be useful to make the test more readable. So instead of passing in like I'm heavily, how about we pass in username and repository, right? And then we know that we need to update that. And this one over here. And notice, it makes it easier for person reading it to kind of recognize, I see this string ends up here and that string ends up there.

[00:13:41]
And I see that there's a particular response. Now, the other thing we could do is instead of just passing a token, we can actually say token here and now our test is failing because we have an additional argument here, see authorization. This thing gets added to the response.

[00:14:00]
So now we made a more complete test because we can verify that the correct token gets passed into the system. We can verify that the fetch is happening and we can fully control the responses of all the pieces. Make sense?

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now