Check out a free preview of the full Testing Fundamentals course
The "Mocking Dependencies" Lesson is part of the full, Testing Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:
Steve explores how to mock dependencies in components. Using stubs and mocks allows developers to control the environment, however, mocking too many dependencies can lead to unrealistic testing scenarios. Refactoring code to use dependency injection is a better solution for creating more testable code.
Transcript from the "Mocking Dependencies" Lesson
[00:00:00]
>> Steve Kinney: So let's look at one more example here that I think will hopefully, again, this is like tricky stuff.
>> Steve Kinney: There's a few kind of interesting pieces in this logjam 1. So let's pull it open. So we have this interesting log function. It's supposed to be one that doesn't get you in trouble in production, which means in development, it's a console log, and in production, it will send it to your log server, right?
[00:00:41]
That way you don't, who has left a console log in their code base and merged it into main? Everyone has, we've all done it. And so this one at least will send it to your, it's a fake function. But the thing is problematic. This is what I was talking about before.
[00:01:01]
This function is problematic for a number of reasons. And we're gonna show how to solve the problem with a mock, and then we're gonna show how maybe you just don't need to do that, right? Okay, so problems here is, guess what mode? This is like the same thing as,
>> Steve Kinney: Same basic idea this is either development, test, or production.
[00:01:33]
But guess what this is, guess what? This is in your test. It's test, all right, so how do you test code that only behaves a certain way in production or development if you're in test. [LAUGH] and then also this again, we didn't pass this function in. When you require this file, it's also gonna import this function.
[00:01:58]
Good luck, right? And so if you wanted to test how it behaves in production. Well, you're gonna send stuff to the server, right? Because by calling this, you require this log.js. It requires send to server tough, right? And so you're like, I just won't test that code, okay?.
[00:02:23]
That's a life choice, I guess or we can figure out how to get around it. Okay, so we've got a few options here. And let's take a look at both of those cases. All right, so let's talk about the environment variable thing first. Cuz this one is pretty easy.
[00:02:48]
You got two choices one a little bit more glamorous than the other one. So what we can do is we can say like, describe, and you can nest these if you want. You can say something like. You describe development, and what that does? It just makes a little outline of your tests prettier.
[00:03:06]
That's all, it just indents it. That's it, I wouldn't get too carried away, but you can do it occasionally. And so in development, we'd like to see if, Console log is called, right? We did earlier, but we need to say let's say before each.
>> Steve Kinney: You could literally just take the process and or whatever object and replace the value and put it back when you're done.
[00:03:41]
Or you can do this,
>> Steve Kinney: Stub the environment in this case mode. That means just for these tests, the MODE that we're in is gonna be development instead of test, right? And after each put everything back, everything you mess with in the world after each test, put everything back, right?
[00:04:05]
We were being a little lazy before we didn't do that, but we should. So now, we'll stub the environment. And now we wanna say,
>> Steve Kinney: It logs to the console In development mode.
>> Steve Kinney: Right and so in this case we'll say like that logs by the way made earlier.
[00:04:32]
Let's give it another shot.
>> Steve Kinney: The property name there and,
>> Steve Kinney: So in development, we can expect that our spy
>> Steve Kinney: On console log was called.
>> Steve Kinney: Except if you name the variable two different things, it doesn't work. Okay, and we can do something similar, right? We could do something for production where we say,
>> Steve Kinney: Go ahead and I'm going to steal this real quick.
[00:05:44]
>> Steve Kinney: Where now we're gonna say that the mode is production. Yeah, I'm gonna write a test relax. It should not call console log, and if Production.
>> Steve Kinney: And all we'll do here is grab this code,
>> Steve Kinney: And we'll say honestly just not open even called it all I don't recall what you were called with, all right?
[00:06:25]
And what are you angry about? Got a little carried with my copy and pasting there.
>> Steve Kinney: Right, so, now we have the same thing because we're spying on it. We have two different environments, right? Where we say in development, we should have called Cons log in production it should have never called Cons log, and we now know that both are true right?
[00:06:50]
That's not the hard part. What are we gonna do, yeah.
>> Speaker 2: Sorry.
>> Steve Kinney: Yeah.
>> Speaker 2: Can you define Stub real quick?
>> Steve Kinney: No [LAUGH] replace it with another value that's not real and put it back later. Yeah, thank you, if there's somebody who's really into testing they're not gonna accept that answer.
[00:07:11]
But that's the right answer. But I will get added on Twitter later for that or I'm sorry X formerly Twitter. For that definition, but that is the more reasonable answer than anyone else will give you, which is for a hot minute, regardless of what reality is, can this be the answer?
[00:07:32]
Yeah, and then we put back the real answer later. Yeah the fact that and how is that different from a mock, I don't know, like a mock is a function, stub is a value who cares? It's a fake value that we put in there for a second and then we put the real stuff back later, right?
[00:07:54]
Is the overall version here? So we, So for a minute, the environment was always test, or the mode was always test. We lied for a hot minute, said it was development, put it back afterwards, all right? Then in another set of tests, we lied for a minute, said it was production, and then put it back afterwards, right?
[00:08:17]
And this case we're spying on console log. And this is for everything people complain about mocking and spying. This is fine cause we didn't change any behavior. We just set the modes of development of production for a minute and saw if a function got called, nobody got hurt in the making of this.
[00:08:35]
Now, let's hurt some people. What are we gonna do about this? This thing just pulls in, send a server. No matter what, right? And tough. Now, the answer is dependency injection. Well, let's make believe you can't do that for a minute, right? Right, so actually this test in production, send a server doesn't do anything.
[00:08:58]
It just returns a string, but it would theoretically, be calling some fictitious server that would fail, right? And that's not great. But because of the way that we wrote our code, not because testing is hard, because this code is hard to test, we have to do terrible things, right?
[00:09:17]
This thing, log, just pulls in this dependency. Nothing we can do about it.
>> Steve Kinney: We're gonna do something about it, but if it makes you feel bad and it hurts your feelings, that is the right feeling. So there is two more we'll get to the other one in a second cuz it's just a brief moment.
[00:09:41]
One more kind of way that mocking manifests itself in here as well, which is you can do this terrible thing.
>> Steve Kinney: This,
>> Steve Kinney: Now, when any of this, like the test file gets loaded first, this will actually, even if you write it here, effectively, it'll be up here.
[00:10:14]
What this will do is, Vitest or Jest, whichever one makes you happier, it's gonna get to this test file. It's going to take all these it's gonna move them all to the top. And then it's going to say, if anyone tries to import that file, let's replace it with a fake one,
>> Steve Kinney: Right, and now, instead of requesting that file, if you give it nothing, it's just gonna automock, which is just put in dummy values that don't do anything.
[00:10:50]
So this alone will stop this thing from calling out to the server, right? Let's verify that, which is. Let's say that no matter what, this should throw.
>> Steve Kinney: Right, weird. This thing should blow up no matter what. And it doesn't. You know why? Cuz it never gets imported.
[00:11:22]
All this does, if you have something that's pulling in some other like Axios or whatever, right? This will basically say, no, no, no, no, no, you're not gonna require this one. I'm gonna give you a bunch of fake stuff. It's fine. And basically this is where we get into trouble, right?
[00:11:42]
Cuz I'm gonna do this all the time now. I'm gonna knock out everything. All right, you've now made a mostly worthless. This works in a pinch, right? And you can even give it a second argument, which is,
>> Steve Kinney: So, if I look at send a server, it exports one property it's not a default export.
[00:12:09]
So it's the send to server. So now what we can do here is,
>> Steve Kinney: So now we have not only mocked it out, we have replaced it with this BS version. The only nice thing we can do now is we can expect that logs I was not called.
[00:13:08]
>> Steve Kinney: Op, what's she angry about?
>> Steve Kinney: This is gotta be a function, sorry.
>> Steve Kinney: Nope. [INAUDIBLE]
>> Steve Kinney: We gotta pull in from that file.
>> Steve Kinney: So, here we are saying, anytime anyone asks you for send-to-server, don't give them the real file, give them this fake version, right? And then we too ask for the fake version, right?
[00:14:04]
And our other code that we don't control called it, and we hope for the best. That face is the right reaction because you know what you could just do? You could just have this log function takes some additional parameters, including a function you would like it to call in production, right?
[00:14:22]
You could have a default to all those values, and you can just pass in stuff and be like, hey, yeah, by default, call the sender server. But call whatever I passed in, you could do something like this. Log, message, and let's say mode.
>> Steve Kinney: And,
>> Steve Kinney: And now you can just say hey, if mode is equal to production, not equal to production, otherwise.
[00:15:32]
>> Steve Kinney: And now this function is not only easier to test, because you can pass in the mode, you can pass in the production callback. It also means that, let's say you want it for to go to your error reporting in production, but you actually also want this to do something else, maybe, in test, or in staging, right?
[00:15:50]
Now, you can, even as you use this function, you can pass in all of this stuff too, and you have a more flexible function in general, right? And so now, guess what you get to delete? I don't know, all of this, right? Honestly, you get to delete all of this.
[00:16:13]
You get to this can just become a function that you pass in to make sure it got called with the things you wanted to and you're not simulating reality or mocking out the world. Cuz if you are, let's say you wanna mock out axios, you gotta mock out all the axios, right?
[00:16:40]
There's a way to import the actual one and then only mock out wanted, you're going to mess it up, right? Versus, if you get to the point where you can pass in the code you want, right, you get something that's more flexible into day to day use because you can pass in different use cases, right?
[00:16:56]
And also, you get something easier to test, because now it's just JavaScript and not this crazy mocking and spying stuff that you're doing all over the place, right? Again, these tools are available. If you need them, they are there, use them, right? But outside of random numbers and a few other things and dates, which we'll talk about in a second, just make your life easier and pass in what a thing needs instead of if it's pulling the stuff in internal to that file, you have no control, right?
[00:17:24]
And that's when testing gets hard, right? Testing should be as easy with add, we passed in two and two, right? We passed in everything that function needed to get his job done, right? And that should be true for everything we write because then we can just pass in, the cool thing about those mock functions, which are just this version is that's just a harmless function.
[00:17:44]
I mean, this could have an implementation here that does whatever you want. But you pass it in as a little scientific probe, right, and then ask about what it saw when it was inside the function. That's totally easy to reason about. Things get crazy cuz we're all kind of a little stressed out about one dependency.
[00:18:01]
[LAUGH] How many files have one dependency, right? Doesn't happen all that often, right? And so with these patterns we can just kind of passed off and this will work for a React component as well it'll work for a regular Javascript class as well it'll work for a function, right?
[00:18:18]
The one pattern that you might see a little bit more often is you can imagine that this thing could eventually take 13 different arguments, right? So what you'll eventually see is something like this, where it's like, you'll have the core thing and then,
>> Steve Kinney: Yeah, and you'll have one object.
[00:18:41]
This is a little bit easier to get away with in TypeScript too, because you can say exactly what they are, and you get the autocomplete and all that kind of stuff. But here we could do production callback. I hate the name of that function, but I'll just put a fake function in here for now.
[00:19:23]
>> Steve Kinney: So now the second argument is by default an empty object, right? If any of those values are missing, here are the defaults. So now you can basically passing just the things you need and stuff along those lines. So that would end up being,
>> Steve Kinney: Right, and so I probably got some other tests in here to work through in a second, but generally speaking you have these options, you can pass in what you need, so on and so forth.
[00:20:09]
Again, works for React components, works for functions, works for classes. Also it's just nice cuz it makes your things totally reusable, right?
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops