Check out a free preview of the full Enterprise UI Development: Testing & Code Quality course

The "Creating Mocks" Lesson is part of the full, Enterprise UI Development: Testing & Code Quality course featured in this preview video. Here's what you'd learn in this lesson:

Steve demonstrates how mocks allow developers to test code they don't control without requiring the necessary functions or APIs to be called. This is useful when the external function or API isn't available or would result in increased fees for usage. Mocks can be passed a custom implementation so mocked return values can be used later in the test.


Transcript from the "Creating Mocks" Lesson

>> We're going to change gears a little bit. And there is a topic that if I was gonna talk about unit and component testing, I technically have to talk about, and I've alluded to it, so I also have to talk about it. But I think should be also like coverage reports on the other end, should be treated with a certain amount of suspicion, right.

And it's mocking, stubbing, spying test doubles, 1000 different terms like unit integration and end to end test, all those terms. At this point there are people who definitely know what the difference between all those things are between a test double and a mock and a stub because it's their personal brand.

For most of us, you can probably just use them interchangeably right? Again, this is a test on making maintainable large code bases, a full-on unit testing course will get further into the weeds, but in my general day-to-day practice as a practitioner, you use them interchangeably. It doesn't really matter, don't worry about it.

The concept is the same which is, you should probably only have your tests fail on stuff that you control, right? And so sometimes you need to fake things you don't control. You don't want your test breaking because the backend is down, right? They should be on call for that and they should fix it.

Or there are things that you don't wanna do. A lot of the good use cases for mocking and stubbing are more applicable to the back-end. For instance, you probably don't want to hit the Twilio API to send a text message as part of your test suite. You mock that one.

You say like, did you get a fake thing? Did you get requests? Pretend like you're Twilio and that you sent the text message, but one, that API costs money, and sends text messages, [LAUGH], right? You don't need your test to fail because you're out of money for things you shouldn't be paying for anyway.

You should pay for Twilio, but not in your test suite, so stuff like that. Like running a credit card, probably don't want to do that as part of your CICD process. Mock and stub that out. Front-end engineers you got less of a permission structure, right? Yes, the back end API's, but I'm gonna caveat that later, so good luck there.

Other than maybe talking to an API, that's basically it. But again, and we'll also see that if you had dependency injection and you were able to pass in the function that should call the API, you could just have normally passed in an empty function and not needed to mock or stub anything, right?

And we can argue is that really a mock or stub? I'm not having those conversations right now. But I will show you how to do it, but I would say every time you do it, and if you are doing a code review and you see one of these lines, you have my permission to, in the code review be go like, what you doing here, right?

Are you sure you need to do this? But let's talk about it, I think it's super useful, but it's a way to basically, replace functionality and either use something fake or in a few cases, be able to have some introspection on what's going on, cool. So we got mocks, we got spies.

I think I'm gonna start out with just kind of a slightly silly example, because I think it'll prove the point, I think. And I think this is the file that I added randomly that was causing some problems earlier. Let's go back to the main branch, not my weird actions branch, discard my auto update, right on.

And I got this directory called logjam, and logjam is silly. It's got this arithmetic object. It's also got log, which is kind of like console log, but more different, it's console log. It just calls console log. You just pass log error, warner info as the first argument, it's console log.

And I've got this ridiculous test file in here, in order to just play around a little bit. This is basically a sandbox to play around in. So if you're using vitest you have this vi object. If you are using jest, you will change two things. You will change the word vitest to jest, you will change this vi to jest.

Everything else that I do will be the same, sweet. So if we grabbed that, is it export default, export const log. Right, this is a function that I can pull in, and I can do stuff with. One of the things that I can do is I can choose to either mock something or spy on something, right?

A mock is super interesting. The way that you write it is a const, And this returns a function. This function out of the box doesn't do anything. It's just a no op, empty function, right? What I can do here is I can do mock, mock, one more, mock.

We're first talking about the good cases where I'm okay with you mocking and stubbing, to be clear. And that's cool, cool, cool. And we could then do stuff like, expect(mock).to, HaveBeenCalled, right? So for instance, let's say we have a function of, like we talked about, it'd be great if you could pass in the thing that makes the API call, o something where it's like, hey, I'm gonna give you a callback, right?

And what you probably wanna do is verify, hey, did my function that was supposed to call a callback, did it call the callback? Well, how would you do that, right, normally? Well, you're all very smart, I'm sure you could figure it out. You might keep some weird variables around, and then in the callback, it would iterate the int, and then you would check to see that the number was a number.

Again, you run into who tests the unit test at that point, right? If you mess up the logic there and your test fails, life's not good. So we can go ahead and we can see this file here and go over to test. That's not even the right test.

I can do, Vitest, what was this filename called? test.ts.
>> Well, it should still let wrap, yeah, it should still work though.
>> It doesn't hit the dot. Move test.
>> Cool.
>> You still have your to-do though.
>> Gotta remove my to-do. Gotta reopen that file. Life is hard.

Cool, move that to-do so that it passes. And you can see that this toHaveBeenCalled. So one of the things that a mock and a spy does, when I said it was an empty function, I wasn't lying. I was, but I wasn't really lying, I was spiritually honest. It's a function that doesn't do anything, but it does keep track of every time that it was called, right?

And it also keeps track of the arguments that it was called with, right? And so if you're passing a callback, or you have another function that you're expecting, hey, this thing should call fetch with the right API, or it should call, you might choose to mock out local storage, right?

Things that you don't really have access to in the browser, that you don't control, you wanna make sure that under the hood, this thing should have put this thing in local storage or it should have called Fetch. It should have called this other library, it should have called the Twilio SDK with these arguments, right?

Mocking something will allow you to do that and that's testing your code. Just faking out the world because you don't like it is not a great use of mocks, because like it'll get your test passing at what cost, right, at the cost of your code is divorced from reality, right?

So unless you are truly using it to exercise and test the code, I would be suspicious of mocking, right? There are probably better ways to do it, but toHaveBeenCalled. And then we can do, that's the big font here is called with, toHaveBeenLastCalledWith, toHaveBeenCalled a certain number of times, toHaveBeenCalledOnce, super interesting toHaveBeenCalled, toHaveBeenLastCalledWith.

That test fails, cuz you can see it was expecting one argument, an array of one with that word in it. It was called with an empty, And now it passes, right? So if this was code that calls code that calls code, but maybe you're passing it in, right, and you wanna follow it down the chain, you could test that function.

It's basically a way to look at that thing and see it. And that's a really powerful use of a mock. If you needed it to also do something, you could pass that in here as well. So you could say something like, Just until the other ones pass, I'll make that optional.

Right, so now you can see now it will do a thing. I'll show you just the thing that I'm not going to talk about otherwise. This is something that I use a lot, especially as I'm developing, I'll use a snapshot. If you don't return stuff, you don't see it.

So even my dumb example, my test helped, right? That's my snapshot. A lot of times if I'm working on something, I wanna make sure it does the same thing as I refactor it. You can use a snapshot or an inline snapshot, snapshot writes to a file, inline snapshot puts it right there.

And as you're iterating, you're like, cool, I don't have tests for this function this is what we talked about before that 80%, this is a thing that does things. I wanna pass in some callbacks. I wanna make sure it does the same thing as I refactor it, and then I'll write the real tests or something like that.

It's a great way to get you up there. Is it the best testing strategy in the world? No, you saw me before when I saw an outdated snapshot and I just ran update snapshots and moved on with my life. Super brittle, but I think in the same way, I don't use code coverage, necessarily, as a real analysis of whether or not I have good tests.

I don't use snapshots as real tests. But I do sometimes use them for things I don't totally understand and I'm still figuring out. I just need it to work the same way. I will use it as I'm developing a lot, and I think that's okay, too, but being like, this is easy, I don't have to actually write real tests.

I'll just snapshot everything, until the next person blows them all away and why do you even have these tests? But you can see that it will act like a real function in this case. I can keep track of how many times it's been called, what it's been called with, right?

And like I said, you could choose to mock out third party libraries. And I'll show you a better way to do this, why I'm not doing a whole example on it. You could choose to mock out Axios or node fetch or whatever you want, and then be like, cool, Axios should have gotten called with this API.

And you don't have to figure out how to detangle your code, cuz if it's tightly coupled, you might not have access to a thing that calls fetch, right? My argument, if this is a true course on testing, we spend a whole lot of time talking about mocking cuz I think it's somewhat useful.

I will repeat the previous statement cuz it makes my point better. If your code is coupled together, you can use mocks so you can get test coverage. This is a course on a large application, untangle your code, [LAUGH] right? It's the real answer. A mock will do the trick for you, there are other things you can watch that will show you all the depths of mocking.

But I should mention it because it exists. You should use it when you need it but you should always treat it with suspicion. And you should not be like, I'll just mock out everything because you are divorcing yourselves from reality.

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