Frontend Testing with Jest – Mocks

Posted by Anna Maier on July 5, 2019

Jest is a popular unit testing framework for Javascript. In an earlier post, we have looked in detail on how you can use assertions in Jest unit tests. In this post, let’s look into using mocks in Jest tests.

So, what are mocks?

In unit tests, you want to focus on one functionality only and ignore the logic of functionality you are dependent on. This is where mocks come into the picture. They make it easy to provide dummy objects for dependencies . Additionally, mocks keep track of all interactions with the mock. This way, you can check if your code calls the dependent code in the right way.

The first mock

Let’s look at a very simple example for using a mock.

test('A simple mock', () => {
	const myMock = jest.fn();
	myMock();
	expect(myMock).toHaveBeenCalled();
});

In Jest, you create a mock object by calling jest.fn(). This already creates a fully functioning mock. Any call on it returns undefined by default. After calling it, you can assert that it was called using the toHaveBeenCalled() matcher.
In most cases though, you want to specify a return value because your code needs something to proceed. Let’s look into that next.

Specify a return value

You can provide a value to be returned by the mock by calling the mockReturnValue() function. No matter with which arguments the mock is called, it always returns the specified value.

test('Always return same value', () => {
	const myMock = jest.fn();
	myMock.mockReturnValue(42);

	expect(myMock()).toBe(42);
	expect(myMock(1, 2, 3)).toBe(42);
});

Sometimes, you want the mock to return one value in the first call, and another one in the second call. With the mockReturnValueOnce() function you can specify the value returned for a single call. The function can be chained, also with mockReturnValue(), to specify the return values for successive calls to the mock.

test('Chain return values', () => {
	const myMock = jest.fn()
		.mockReturnValueOnce(42)
		.mockReturnValueOnce(-42)
		.mockReturnValue(0);

	expect(myMock()).toBe(42);
	expect(myMock()).toBe(-42);
	expect(myMock()).toBe(0);
	expect(myMock()).toBe(0);
});

The example shows that once you run out of one-time return values, it defaults back to the value specified with mockReturnValue(). If you did not specify a default return value, the mock function returns undefined.

Provide a mock implementation

In some cases, it is useful to base the return value on the arguments given to the mock. Jest provides the mockImplementation() function to define a default implementation for the mock function. Analogous to mockReturnValueOnce(), there is also a mockImplementationOnce() function to define the implementation for one single call.

test('Provide mock implementation', () => {
	const myMock = jest.fn()
		.mockImplementation(i => 0)
		.mockImplementationOnce(i => i + 41);

	expect(myMock(1)).toBe(42);
	expect(myMock(1)).toBe(0);
});

As you can see in the example, you can place mockImplementation() before mockImplementationOnce(), but Jest still uses the one-time implementation first. The default implementation is only used when you run out of one-time implementations.

Mocks for asynchronous methods

You can also mock the implementation for asynchronous functions. In this case, just return a promise from the implementation.

test('Asynchronous mock implementation', async() => {
	const myMock = jest.fn(a => Promise.resolve(true));
	await expect(myMock()).resolves.toBe(true);
});

The example shows an alternative way of defining a default implementation: provide the implementation directly when creating the mock. This is fully equivalent to the mockImplementation() function.

For simple use cases, Jest provides the convenience functions mockResolvedValue() and mockRejectedValue() to specify return values for asynchronous functions. Parallel to mockReturnValueOnce(), one-time return values can be defined with mockResolvedValueOnce() and mockRejectedValueOnce().

test('Asynchronous return values', async() => {
	const myMock = jest.fn()
		.mockResolvedValueOnce(42)
		.mockRejectedValueOnce(-42);

	await expect(myMock()).resolves.toBe(42);
	await expect(myMock()).rejects.toBe(-42);
});

Check mock calls

Up till now, we have only looked at one functionality of the mocks: provide a dummy method to call. But it is also often interesting to check that your code calls the method with the correct arguments. Jest gives us a couple of matchers to check this.

We already saw the toHaveBeenCalled() matcher in the very first example. There are several convenience functions to check in more detail how often the mock was called (toHaveBeenCalledTimes()), which arguments where given (toHaveBeenCalledWith()) and in which order (toHaveBeenNthCalledWith(), toHaveBeenLastCalledWith()). The following example shows the usage:

test('Check arguments', () => {
	const myMock = jest.fn();
	myMock('a');
	myMock('b');

	expect(myMock).toHaveBeenCalled();
	expect(myMock).toHaveBeenCalledTimes(2);

	expect(myMock).toHaveBeenCalledWith('a');
	expect(myMock).toHaveBeenCalledWith('b');

	expect(myMock).toHaveBeenNthCalledWith(1, 'a');
	expect(myMock).toHaveBeenNthCalledWith(2, 'b');
	expect(myMock).toHaveBeenLastCalledWith('b');
});

If the convenience functions are not enough, you can also access the call data of the mock directly. Each mock object has a property mock to register calls, return values and instances of the mock. The information about the calls is registered in an array, one entry per call.

test('Check the first call to myMock', () => {
	const myMock = jest.fn();
	myMock('a', 'b');
	expect(myMock.mock.calls[0]).toEqual(['a', 'b']);
});

Check mock return values

If you provided a custom implementation for your mock, it might be worth checking that it actually returned the correct value. Analogous to the functions to check the mock calls, Jest provides convenience functions to check the return values of the mock. There are functions to check if and how often it returned (toHaveReturned(), toHaveReturnedTimes()), what the result values were (toHaveReturnedWith()) and in which order results returned (toHaveNthReturnedWith(), toHaveLastReturnedWith()).

test('Check results', () => {
	const myMock = jest.fn(arg => arg);
	myMock('a');
	myMock('b');

	expect(myMock).toHaveReturned();
	expect(myMock).toHaveReturnedTimes(2);

	expect(myMock).toHaveReturnedWith('a');
	expect(myMock).toHaveReturnedWith('b');

	expect(myMock).toHaveNthReturnedWith(1, 'a');
	expect(myMock).toHaveNthReturnedWith(2, 'b');
	expect(myMock).toHaveLastReturnedWith('b');
});

The return values are also stored in an array on the mock object. This comes in handy especially if errors were thrown. When an error occurred in the mock, toHaveReturned() and its variants return false. To actually find out which error was thrown, you have to access the results property of the underlying mock object, as shown in the example:

test('Check results with errors', () => {
	const myMock = jest.fn(f => f(1));
	try {
		myMock('ha!');
	}
	catch (e) {
		// ignore
	}

	expect(myMock).not.toHaveReturned();
	expect(myMock.mock.results.length).toBe(1);

	const errorResult = {
		type: 'throw',
		value: new TypeError('f is not a function')
	};
	expect(myMock.mock.results[0])
		.toMatchObject(errorResult);
});

Cleaning up

It is generally not good practice to re-use a mock across unit tests, because tests should be independent of each other. However, sometimes you cannot avoid re-use, but still want to prevent tests influencing each other. The function mockClear() clears the call data of the mock, so that you can start from zero. The function mockReset() goes a step further and also clears the implementation and result data.

describe('Cleaning up', () => {
	const myMock = jest.fn();

	beforeEach(() => {
		myMock.mockReturnValue(42);
		myMock();
	});

	test('Clear the call data', () => {
		myMock.mockClear();
		expect(myMock).toHaveBeenCalledTimes(0);
		expect(myMock()).toBe(42);
	});

	test('Clear calls and implementation', () => {
		myMock.mockReset();
		expect(myMock).toHaveBeenCalledTimes(0);
		expect(myMock()).toBe(undefined);
	});
});

Bonus – Spies

Occasionally, you only want to mock a single function of an imported module. With Jest spies you can put a wrapper around this function and spy on its usage.

test('Only spy on exising function', () => {
	const module = {
		fun: a => a + 1
	};
	const spy = jest.spyOn(module, 'fun');

	expect(module.fun(1)).toBe(2);
	expect(spy).toHaveBeenCalledWith(1);
	expect(spy).toHaveReturnedWith(2);
});

Note that by default, the original function also gets called. This is different to most testing frameworks. You can prevent this by providing your own implementation with the mockImplementation() function we encountered already. With mockRestore() you can reset the spy to the original implementation.

test('Spy + Mock', () => {
	const module = {
		fun: a => a + 1
	};
	const spy = jest.spyOn(module, 'fun');
	spy.mockImplementation(a => a + 2);

	expect(module.fun(1)).toBe(3);
	spy.mockRestore();
	expect(module.fun(1)).toBe(2);
});

Mocks are a complex topic and we only scratched the surface. If you want to know more, have a look at the Jest documentation, especially the part about mocks: https://jestjs.io/docs/en/mock-functions.

About the author: Anna Maier

Software Developer at TOPdesk with a passion for good structured code, pragmatic solutions and learning.

More Posts

Twitter