Jest Spy vs Mock — when to use what!

Gunjan Kalita
4 min readFeb 5, 2023

--

By Kevin Ku on pexels.com

If you are stuck with Jest Spying and mocking and not sure of when to use which one of these, this is your blog!

Let’s understand this step by step; with some associated concepts and code examples.

What is testable code!

Your code is testable if it returns the same output for a given set of inputs, regardless of environments. An environment can be

1. Database
2. Time of day
3. System delays
4. Network delays
5. OS/ System configuration

Let’s understand it with an example.

/**
* Function returns morning, afternoon or evening
* depending on when it is called
*/
const whatTimeItIs = () => {
const date = new Date()
const hour = date.getHours()
if (hour < 12) return "morning"
if (hour < 16) return "afternoon"
return "evening"
}

Above is an untestable code. Simply because it will give you different output depending on when you call the function, failing all your assertions.

How to test untestable code !

Simple answer to this is spying. Spying is the process of looking for a specific function and returning a different value when it is called (like a trap).In our function above, the only reason it is untestable, because the Date function returns a different timestamp every time it is called. We proxy this and return a unique value every time you end up writing stable test cases.

Jest Spying is done via a function called spyOn. Below is an example of how to do it.

describe('What time is it:', () => {

it('Verify that 15 returns afternoon', () => {
const mockDate = new Date(2021, 3, 24, 15, 0, 30, 0)

// Create a system spy.
const spy = jest.spyOn(global, 'Date');
spy.mockImplementationOnce(() => mockDate);

let output = functions.whatTimeIsIt();

expect(output).toEqual('afternoon');
// Check if the spy was called
expect(spy).toHaveBeenCalledTimes(1);
});
})

What’s happening above ?

1. A date object is created that points to a specific time. (mockDate)
2. jest.SpyOn set to look for Date function call which belongs to the global object. This is the trap we set.
3. Next line simply says what value to return when the Date function is triggered. This value will be returned only once. Subsequent calls to Date will not be affected.
4. You are all set to test the function.

So, whenever you face code which is untestable, please use jest spying to deal with it. And please do not forget to remove the traps after each test you write. This is how it is done

describe('What time is it:', () => {
afterEach(() => {
// Restore original implementation
global.Date.mockRestore();
})
//... rest of the code

Note: You can also make it testable by making date an argument to the function. This is smart, but will it always help !!

Mocking is an equally important concept in testing which you might end up using more frequently. When you write code for production, frequently you will find that some function is internally calling some other functions to do their job. When we are testing our code ( specifically unit testing), we want to be as much specific and want to test the function and not on something it depends on. Also, any bug on these dependent functions might cause our tests to fail. This is where we use mocking. This is done by jest.fn function.

Let’s understand the code below. We have two files, utils.js and support.js. Utils exports a function which depends on some functions provided by support.

// utils.js 
const { support } = require("./support")

/** a function to perform some calculation */

const performSomeCalculation = (a: number, b:number) => {
const value = support(a, b);
return value * b;
}
// support.js 
const supprot = (a: number, b: number) => {
// does something fragile.
return a ** b;
}
module.exports = { support }

Now let’s try to test the utils function with some mocks in place. Below is a code sample.

const deps = require("./support")
const performSomeCalculation = require("./utils")

describe('PerformSomeCalculation', () => {
it("should return x for y, z", () => {
const mockSupport = jest.fn();
deps.support = mockSupport.mockReturnValue(5) // let's say
const value = performSomeCalculation(5, 7);
expect(value).toEqual(35)
// it passes
})
})

Let’s understand what’s going on
1. create a mockSupport function with jest.fn. This is our mock.
2. add a return value of 5.
3. call the function
4. Assert with a value which is nowhere near the correct value. (please donot confuse here )

This is mocking.

What the …. !

Yes, knowing every time about them makes you more confused. Let me point out what you are missing here.

The main difference in spying and mocking is, when you are spying you are not undermining the entire function implementation, rather you are just targeting a few lines of code which is making it untestable. But in mocking, you are undermining the entire function implementation and you are just returning the value.

When to use what

As a rule of thumb, always use spy when you face an untestable code.

Use mock when you see your function being critically dependent on other functions. You must mock those functions in such cases.

Below diagram will help keep things together.

spy vs mock ( jest)

Let me know your doubts :)

Also, if you are liking the content I am producing, then do buy me a coffee :)

--

--

Gunjan Kalita

Javascript | React | Node | MongoDB | MySQL. SDE 2 at Swiggy | DM for a free CV review