Mocking TypeScript classes with Jest

David Guijarro
5 min readNov 19, 2017

--

Imagine the following scenario: you have a TypeScript class that relies on some another class to perform its task.

Now, since you’re an awesome developer, you want to write some unit tests for your class. Let’s say that you want to use Jest for your unit tests, because it’s quite an standalone tool and because it’s cool as well.

But you’d like to isolate the class you’re testing from the class it depends on, because that’s what awesome developers do. So you will obviously need to mock the dependencies on your tested class.

Well, it turns out it’s not that straightforward. We recently ran into this same problem when working on the base of our new web architecture at car2go, and it took us a while to figure out how to make it work. If you find yourself stuck at the same problem, this post might help you out a bit.

First things first

As an starting point, include the following lines to your package.json file:

We will be using the ts-jest npm module to make Jest able to work with our TypeScript files. Running npm test in your CLI will make run the tests. If you try to run it now, Jest will complain about not finding any tests, though.

Welcome to First Class

Let’s now create our first TS class. For this example, we will be writing a class for dealing with a (fairly standard) User entity in a traditionally RESTful way: get all users, get one specific user and so on.

Since we’re awesome devs, we will write the test first:

This is a fairly simple test: we expect the Users class to have an all() method that returns an array of users.

Let’s implement the actual Users class:

We’re just returning an array of users directly from the Users class. If we run the tests now, this is what we get:

It feels sooo good when your tests pass for the first time!

Good, so our (very simple) test is passing now. Thumbs up!

Making dumb code a bit less dumb

Obviously, at this point we would probably want our Users class to return real data. For this example, we will create another class as an “adapter” to an API (Reqres, in this case, just for demonstration purposes,) but in real life data can come from a database as well.

So, let’s create a folder named “common” and a “http.ts” file with a Http class in it. This class will use the awesome axios to make the requests.

Of course, for this super-simple example we could make the request directly through axios, but writing this kind of adapters is always a good idea to avoid repeating a lot of boilerplate code.

As a next step, we will modify the original Users class to use our brand new Http class and fetch some real data from our API:

If we run the tests again, this is what we get:

Isn’t it nice? Isn’t it?

Not that fast, cowboy!

So, yeah, the unit tests are passing, I give you that. But this is not an ideal situation at all. The tests are not isolated. The request that the Users.all() method is triggering is travelling the whole chain of dependencies, from the Users class to the Http class, to axios, to the API and back.

This is easy to notice if, for example, you turn off your wifi and run the tests again; they will fail this time throwing a nasty Network Error from axios (believe me, I tried.)

This sucks because unit tests should be isolated. If they’re not isolated, then they’re not unit tests, they’re something else (integration tests, some might argue.)

Let the mocks out!

So let’s mock the Http class and then use the mock for our User class test, instead of the real class. Fortunately, Jest makes this pretty easy, but there are a couple of gotchas to which we’ll get later.

The mocked Http class looks like this:

First two gotchas: the mock needs to 1) have the same filename as the mocked class; and 2) be inside a folder named __mocks__ inside the folder that holds the mocked class.

Your life will be much easier if you organize your folders like this.

There are probably ways of changing this Jest default behaviour, but observing this first gotcha will save you a lot of head-scratching (we already scratched our heads for you!)

There’s one last step we need to cover. We need to instruct Jest to use the mocked Http class when running the test for the Users class. For that, we just need to add the following line to the users.spec.ts file, right after the import statements and before the first describe block:

jest.mock(‘./common/http’);

If we run the tests again now with the wifi turned off, they will still pass. So we can affirm now that the tests are effectively isolated. We’re awesome as that!

When things go south

Life’s great when API endpoints or database queries respond as they should and all, but let’s face: even the best API or the most resilient DB crashes into the ground sometimes.

Even more: if you’re writing client side code, then you can be sure that at least one user is going to have a crappy Internet connection at some point in time.

So we need to be prepared for things going south. And our unit tests need to cover error things going south as well.

Let’s modify our spec file to cover an hypothetical error case.

The modifications are not that much, but again it took us a while to figure them out. Basically, the steps are:

  1. [line 2] Importing the dependency to be modified. Since we are telling Jest to replace the real class with the mock one on line 5, we’re going to be actually modifying the mock class.
  2. [lines 21–28] Creating a new test to cover the error case.
  3. [lines 22–24] Modifying the Http class prototype to change the get() method so that it returns an error instead of an array.
  4. [lines 26–27] Checking that the output from the tested method is now an actual error.

Third gotcha: since the Users class is creating a new instance of the Http class inside its constructor, we need to access the Http prototype directly in order to change its behaviour. This is obviously because ES6 classes are just syntactic sugar for the good ol’ prototypical inheritance.

🛳 it!

That’s all. Hope this was helpful. You can continue being awesome now.

If you’re the kind of awesome developer that prefers checking out the code directly, feel free to take a look at the accompanying Github repository.

Thanks for reading!

--

--

David Guijarro

Frontend Chapter Lead @car2go. Sometimes I can feel fullstackness growing inside of me 🙊