JavaScript Testing Practices and Principles

Using a __mocks__ directory

Kent C. Dodds

Kent C. Dodds

Professional Trainer
JavaScript Testing Practices and Principles

Check out a free preview of the full JavaScript Testing Practices and Principles course

The "Using a __mocks__ directory" Lesson is part of the full, JavaScript Testing Practices and Principles course featured in this preview video. Here's what you'd learn in this lesson:

Kent walks through a test from scratch that tests the user's module. Then Kent shows the importance of being explicit in tests and discusses edge cases for this implementation.


Transcript from the "Using a __mocks__ directory" Lesson

>> Speaker 1: For this, we're gonna be in source in the server side, source controllers, and users.todo in the test. And this is actually, I'm gonna be doing this piece for us.
>> Speaker 1: And this test is gonna be responsible for testing the users module. So the users controller exposes a couple express middleware.

If you're not familiar with express, it's totally fine. Just think these are just functions that accept some special objects. And I'll give you function that can generate those objects for you. So you shouldn't have to be too concerned about how this all works. But it exposes a couple of things that our endpoints to interact with our database.

Our database has is get users, update user and delete user somewhere. Yeah, dual user. And these endpoints, just protect those, make sure you're authenticated, all that kind of thing. And it uses the request object and response object to communicate as middleware as it does in Extract. So we're gonna write some test for this.

Most of these functions are exposed. And these are still technically unit tests. Each one of these functions is considered a unit. [COUGH] Yeah, to get this started, I think, the first thing that I want to test is, generally, when I'm starting testing in a new module, it's never been tested before.

Especially in a new project, you want to go to the path of least resistance, make it as simple as possible. And if I'm looking at all of these, I'm thinking the simplest function as maybe this authorize function. Actually, yeah, that probably is the simplest function. But we're not going to do that one, cuz I want to show you the get users one instead.

But I would probably start with the authorize, if I'm perfectly honest. But yeah, we'll do get users. And so, what's the use case we want to verify when testing this, a function. We wanna verify that based off of the, actually, it doesn't even use the request object anyways.

So based off of the fact that it's given a request and response, the request can be anything. The response will need to be called with .json called with all of the users that are in the database. But those should be safe users, these users to .json which you wrote tasks for earlier.

And then, if there are no users in the database, then we'll send a 404. So let's go ahead and we'll do a happy path test first. So we'll test that get users returns all users in the database. And then, this is going to be an async test. So this is an async function, where you want to be able to use a rate because it is the best.

So we'll add a sync to our function declaration here or to our error function there, so we can use a wait when we call this get users thing, okay? So to call get users, we're going to need to get it. So import, get users. Here, we'll just input everything that has users controller.

From one directory op users. And then, we'll say await usersController.getUsers, and it's gonna take a request and a response. And then, we'll create our fake version of those things, kind of injecting those arguments here. The request doesn't really need to have anything, but the response needs to have a json function and we can make that a jest function.

So response, json function. It also needs a status but in our tense we don't have it, it's not going to be calling status, so we can leave that off for now, cool. So we expect it had been called one time, so it's around this testing and around mpm test, and we'll do controllers, star, users.todo.js, something like that.

>> Speaker 1: Okay, cool, so that test passes and we can say it was called zero times, okay, yeah, it's gonna fail, it was called once. So there are a couple of quick things about this, with regard to this mock function that we've created. There's another assertion here that is toHaveBeenCalled, and that will work also.

The reason that I am explicit about how many times it was called is because perhaps somebody comes in and has a little typo and duplicates this, or something. And now, this will actually fail my test. But it would not fail if it were just two of them called.

So I want to be explicit about how many times this thing responds, to avoid situations like that, so I definitely recommend doing it. Being explicit about that. And then we also want to verify what it was called with, so we can console.log(res.json.mock.calls) the first call. And [COUGH] looks like there are no users.

So it's an empty users array and in fact that looks like there's a value. That should probably be the (users && users.length) or something like that. But yeah, in any case, we'll stick with this. So the problem is that our database in our test has not been initialized.

So there are a couple of ways that we can solve this. Right now, this is actually not a pure unit test, so from the purest standpoint, a unit test is one that mocks all dependencies and only tests the things that are within the module. And so, if we were to make a pure unit test, we would provide a mock for utils off, and mock for utils database, so that it's focused on this unit.

So sometimes, if your like unit test pure is say no, no, mock everything. Otherwise, it's not a unit test you're writing an integration test. Well, I actually don't care what you call it. It's an integration test, unit test, I don't care. What I care about is confidence that I'm getting out of my test, and if I were to mock all these things.

And then, I would have to write additional test to get that confidence back that things are working together properly. So I'll just continue working with these things and I can maybe just initialize this database to have some users. And so, I'm not gonna show you how all of that magic works, because it's not really relevant to this workshop.

You can go check out the code for it later. But suffice it to say that we do have a utilities module that we can import. So we import initDb from Today I learned, server-test-utils. You can actually find that it's in the projects, server_test. So you can find that if you want to see what that's actually doing.

So before every single one of my tests, I want it to initialize the database that is an asynchronous, so I'll wait that, and then with that, I actually get a list of users. So this init process generates some random users. Every single time, they're totally random, totally different.

And that's generally what I would recommend is, in some scenarios, if you've got a real database, this can actually really slow down your tests. And so, you really have to kind of weigh the trade offs here because this will bring you a fair bit more confidence. You can make sure that each test is running in total isolation from another.

And if the database is totally messed up in this test, then you just call initDb again, and everything's clean again. And a real application where the database is big and there's a lot of data and that takes a long time. Then an alternative to this would be before all of the tests are run, let's just initialize it with some fake data.

And hopefully, you can have fixtures that all the tests shared, so that you know what should be in the database at any given time. And then every test just cleans up after itself. So you have to weigh the consequences either way. But, yeah, for our cases the optimal would be to just totally wipe it clean for every test.

If that's something that you can do.

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