Testing Fundamentals — Mocking

Utkarsh Srivastava
smallcase Engineering
10 min readMay 4, 2022
Photo by Juanjo Jaramillo

What is mocking?

Mocking is a technique used to isolate the unit being tested by replacing dependencies with objects that you can control and inspect.

A dependency can be anything your unit depends on, but it is typically a module that the unit imports.

A unit under test may have dependencies on other units. To isolate the behaviour of the unit, you want to replace the other unit by mocks that simulate the behaviour of the real unit. This is useful if the real units are impractical to incorporate into the unit test.

In short, mocking is creating a unit that simulates the behaviour of a real unit.

I promise not to use the word ‘unit’ any more in the article

Test doubles — Dummy, fake, stub, mock, spies

In testing, there are several definitions of objects that are not real. The general term is test double.

A test double is an object that can stand in for a real object in a test, similar to how a stunt double stands in for an actor in a movie

There are five major types of test doubles : stub, mock, fake, spies, dummy

Stubs

Stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.

An example can be an object that needs to grab some data from the database to respond to a method call. Instead of the real object, we introduced a stub that returns the hard coded data that should be returned.

Mocks

Mocks are very similar to stubs, but the main difference is mocks are more ‘interaction-based’. This means that we don’t just expect the mock to return some value, but to assume that a specific order of method calls are made.

Mocks are objects that register calls they receive.

In test assertion we can verify on Mocks that all expected actions were performed.

We use mocks when we don’t want to invoke production code or when there is no easy way to verify, that intended code was executed. There is no return value and no easy way to check system state change. An example can be a functionality that calls e-mail sending service.

We don’t want to send e-mails each time we run a test. Moreover, it is not easy to verify in tests that a right email was send. Only thing we can do is to verify the outputs of the functionality that is exercised in our test. In other worls, verify that e-mail sending service was called.

Fake

These objects actually have a complete working implementation in them. But the implementation provided in them is some kind of a shortcut which helps us in our task of unit testing, and this shortcut renders it incapable in production. A great example of this is the in-memory database object which we can use just for our testing purposes, while we use the real database object in production.

Spies

Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

Dummy

When we use an object to stand in place of a real object, but never make use of the object, then the object is called a Dummy. Its usually done to fill the parameter list, so that the code compiles and the compiler stays happy.

In most real world scenarios, we use mocks.

Example of a testing use case

Function Thumbwar is accepting two players as arguments, and calling a getWinner function which returns which player won at the thumb war
thumb-war.js
In this test, we are checking if the output returned by thumbwar is from the arguments passed to it
thumb-war.test.js

Here, all we can test is whether thumbWar is returning one of the parameters passed to it.

This is obviously not a comprehensive test at all.

So we’ll mock the getWinner function.

The easiest kind of mocking is monkey patching.

Code snippet showing monkey patching testing technique. Explained below
thumb-war.test.js

Monkey patching is a technique to add, modify, or suppress the default behaviour of a piece of code at runtime without changing its original source code.

So here, we’re mocking the implementation of the getWinner function after importing it, and then restoring the original implementation after the test is done.

However, This is not advisable because here we are assigning some value to a method of an imported module. This breaks an eslint rule — import/namespace.

It’s not a good idea to do this.

Note: Jest essentially does the same thing to mock too, but it performs some magic in the background, and mocks the module system, which makes the code appear compliant, and we don’t get any warnings/errors

Mocking with jest

When we talk about mocking in Jest, we’re typically talking about replacing dependencies with jest’s Mock Function.

The Mock function allows you to test the links between code by

  • erasing the actual implementation of a function,
  • capturing calls to the function (and the parameters passed in those calls),
  • capturing instances of constructor functions when instantiated with new,
  • and allowing test-time configuration of return values

The goal for mocking is to replace something we don’t control with something we do, so it’s important that what we replace it with has all the features we need.

The Mock Function provides features to:

  • Capture calls
  • Set return values
  • Change the implementation

Mocking utilities in Jest

jest.fn()

The simplest way to create a Mock Function instance is with jest.fn().

math.js

Here we have a bunch of util functions to add, subtract, multiply and divide the parameters passed

app.js

We’re importing the util functions from math.js and using them here .

Now to test the doAdd, and doSubtract functions, we need to create mocks for the add and subtract functions using jest.fn().

Now every-time the add and subtract functions are called from this test, jest would intercept the call, and would call the mocked add and subtract functions instead.

As I mentioned when we were going through monkey patching, this is very similar to what we were doing there , but here jest actually simulates the module system for us, and does some magic to make it look like our code is spec compliant, so we don’t get any eslint errors.

Here we are not testing the implementation of the function, but just whether the functions were called properly. This type of test is useful in scenarios where we want to test whether an event handler being passed to a reusable component as prop is being called correctly from the component.

Jest Spy

Another method of creating a function mock is a jest.spyOn() method. Similar to jest.fn(), it creates a controlled mock.

The key difference is the fact that by default it calls the original implementation.

  • Sometimes you only want to watch a method be called, but keep the original implementation.
  • Other times you may want to mock the implementation, but restore the original later in the suite.

In these cases, you can use jest.spyOn

jest.spyOn() stores, in memory, the original implementation, so in case it has been redefined, jest.spyOn() allows us to restore the initial implementation using mockRestore() method.

A simple usage of jest.SpyOn().

example of jest.spyOn. We’re calling jest.spyOn on add function of math.js module.

Here we are mocking a function, but then restoring the original implementation using mockRestore

Note: MockRestore is useful for tests within the same file, but unnecessary to do in an afterAll hook since each test file in Jest is sandboxed.

The key thing to remember about jest.spyOn is that it is just sugar for the basic jest.fn() usage.

We can achieve the same goal by storing the original implementation, setting the mock implementation to to original, and re-assigning the original later :

Here, at the beginning of the test, we’re storing the math.add function in a variable originalAdd.

Then we’re mocking an implementation for math.add, and testing for the mocked implementation.

In the end, we’re restoring math.add back to originalAdd .

This is essentially what jest.spyOn() does.

Difference between using jest.fn() and jest.spyOn()

There’s no explicit differences as such, but rather we use these two in different scenarios.

jest.fn()

  • You want to mock a function and really don’t care about the original implementation of that function (it will be overridden by jest.fn())
  • Often you just mock the return value
  • This is very helpful if you want to remove dependencies to the backend (e.g. when calling backend API) or third party libraries in your tests
  • It is also extremely helpful if you want to make real unit tests. You don’t care if a certain function that gets called by the unit you test is working properly, because that’s not part of its responsibility.

jest.spyOn()

  • The original implementation of the function is relevant for your test, but,
  • You want to add your own implementation just for a specific scenario and then reset it again via mockRestore() (if you just use a jest.spyOn() without mocking it further it will still call the original function by default)
  • You just want to see if the function was called

Mocking a module

We can also mock a whole module using jest with Jest.mock .

When we mock a module, jest would simply intercept calls to all the functions of that module, and replace them with mocked functions.

Here we’re mocking the Math.js module

So, calling jest.mock(‘./math.js’) essentially sets math.js to:

This is useful in cases where we need to test multiple functions of the same module. This way, we won’t have to mock all the functions individually.

Mocking APIs

  • It’s pretty straightforward to mock api responses using jest.mock()

Example

index.js
index.test.js

Here, when we mock the axios module, all the functions exposed by axios are replaced by jest.fn().

Then we’re returning a mock response from the axios.get method by using mockResolvedValue

This is not an ideal setup, because here, we’ve mocked the response that we need, but there’s a bunch of other things that can go wrong and need to be tested in an api call.

There are a few issues with testing api calls like this

  • We are not actually making an api call here. We are mocking the api handler, and stubbing a dummy response for the get method of the handler.
  • This leaves us open to a lot of bugs that can occur during the actual api call. Since all we’re doing here is just stubbing a dummy response , we can’t test whether the user is passing the required headers, or request params to the api. Suppose you’re making a post request, and the api has changed to accept ‘data’ instead of ‘body’. This scenario can’t be tested here.
  • We are mocking axios here. So now this is a dependency for our test. In the future, if we decide to shift from axios to say fetch, our tests would start breaking. This is cos we’re mocking an implementation detail here.

All these issues can be solved by mocking the api handler at a more granular level, and testing for all of these scenarios, but that’ll be very tedious to do.

This is where MSW comes into the picture

MSW

MSW stands for mock service worker. It’s an API mocking library that uses Service Worker API to intercept actual requests.

The api is intercepted at the network level, so our application doesn’t know anything about the mock, and it actually makes the api call, it can even be seen in the networks tab. The response is just returned by our msw server.

The basic idea is this: create a mock server that intercepts all requests and handle it just like you would if it were a real server.

index.test.js

Here, We’re calling the setupWorker function of msw and passing a request handler to the worker.

we’re using a get request handler, so now after the worker starts, it would intercept all the get api calls made to /albumTitles endpoint, and handle them.

Note: It has handlers for all the functions of rest, and graphQl also.

To respond to an intercepted request we have to specify a mocked response using a response resolver function

This accepts three arguments

  • req — The request (req) object stores the information about a matched request. Can use this if you want to access some property from the request object. For example, req.params or req.headers etc.
  • res — A functional utility to create the mocked response.
  • ctx — a group of functions that help to set a status code, headers, body, etc. of the mocked response.

Note: MockRestore is useful for tests within the same file, but unnecessary to do in an afterAll hook since each test file in Jest is sandboxed.

Credit for code snippets/examples for mocking: ​​https://kentcdodds.com/blog/but-really-what-is-a-javascript-mock

--

--