Mock Functions in Jest
Mock functions helps us make testing of links between code easy, by erasing the actual implementation of a function, capturing the calls to the function (and the parameters passed in those calls), capturing the instances of constructor functions when instantiated with the new keyword, and finally allowing test-time configuration of return values.
You can mock functions in two ways: either you create a mock function to use in test code, or you write a manual mock that overrides a module dependency.
Using a mock function
Let's take for example the case where we're testing an implementation of a function forEach, which will invoke a callback for each item in a supplied array.
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
For us to test this function, we may use a mock function, and then inspect the mock's state to ensure the callback is invoked as expected.
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// The mock function will be called twice
expect(mockCallback.mock.calls.length).toBe(2);
// 0 was the first argument of the first call to the function
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 1 was the first argument of the second call to the function
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 42 was the return value of the first call to the function
expect(mockCallback.mock.results[0].value).toBe(42);
.mock property
Every mock function has this special .mock property, this property is where data about how the function has been called and what the function returned is stored. The .mock property will also track the value of this for each call, so it is possible to inspect this as well:
```const myMock = jest.fn();
const a = new myMock();
const b = {};
const bound = myMock.bind(b);
bound();
console.log(myMock.mock.instances);
// > [ <a>, <b> ]
These mock members are very useful in our tests to assert how these functions get called, instantiated, or what they returned:
// The function has been called exactly once
expect(someMockFunction.mock.calls.length).toBe(1);
// 'first arg' was the first arg of the first call to the function
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// 'second arg' was the second arg of the first call to the function
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// 'return value' was the return value of the first call to the function
expect(someMockFunction.mock.results[0].value).toBe('return value');
// This function has been instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// this is the object returned by the first instantiation of this function
// had a `name` property whose value had been set to 'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');
Mock Return Values
Mock functions could also be used to inject test values into your code during a test:
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock
.mockReturnValueOnce(10)
.mockReturnValueOnce('x')
.mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
Mock functions are equally very effective in code that uses a functional continuation-passing style. A code that is written in this style helps avoid the need for complicated stubs that recreate the behavior of the real component they're standing in for, in favor of injecting values directly into tests right before they're used.
const filterTestFn = jest.fn();
// will make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(filterTestFn);
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls);
// > [ [11], [12] ]
Most real-world examples will actually involve getting a hold of a mock function on a dependent component and then configuring that, but the technique is always the same. In these cases, you have to avoid the temptation of implementing logic inside of any function that's not directly being tested.
Mocking Modules
Suppose we have a class that will fetch users from our API. The class will make an axios call to the API and then returns the data attribute which contains all the users:
// users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
Now, if you want to test this method without actually hitting the API (and thus creating slow and fragile tests), you can use the jest.mock(...) function to automatically mock the axios module.
Once you mock the module you can provide a mockResolvedValue for .get that returns the data we want our test to assert against. In effect, you are saying that you want axios.get('/users.json') to return a fake response.
// users.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
/* or you could use the code below depending on your use case:
axios.get.mockImplementation(() => Promise.resolve(resp))*/
return Users.all().then(data => expect(data).toEqual(users));
});
Mock Implementations
There are some cases where it is useful to go beyond the ability to specify return values and full-on replace the implementation of a mock function. You can do this with jest.fn or with the mockImplementationOnce method on mock functions.
const myMockFn = jest.fn(cb => cb(null, true))
myMockFn((err, val) => console.log(val));
// > true
The mockImplementation method is very useful when you need to define the default implementation of a mock function that is created from another module:
// foo.js
module.exports = function() {
// some implementation;
};
// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
When you have to recreate a complex behavior of a mock function such that multiple function calls will produce different results, you should use the mockImplementationOnce method:
const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
myMockFn((err, val) => console.log(val));
// > true
myMockFn((err, val) => console.log(val));
// > false
When the mocked function runs out of the implementations defined with mockImplementationOnce, it will execute the default implementation that is set with jest.fn (if it is defined):
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'
For cases where you have methods that are typically chained (and thus always need to return this), you have a sugary API to simplify this in the form of a .mockReturnThis() function that also sits on all mocks:
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
myMethod: jest.fn(function() {
return this;
}),
};
Mock Names
Optionally you can provide a name for your mock functions, this will be displayed instead of "jest.fn()" in test error output. You should use this if you want to be able to quickly identify the mock function reporting an error in your test output.
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');
Custom Matchers
Finally, to make it simpler to assert how mock functions have been called, Here are some custom matcher functions you can use:
// if the mock function was called at least once
expect(mockFunc).toBeCalled();
// if the mock function was called at least once with the specified args
expect(mockFunc).toBeCalledWith(arg1, arg2);
// if the last call to the mock function was called with the specified args
expect(mockFunc).lastCalledWith(arg1, arg2);
// if all calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();
These matchers are really just syntactic sugar for common forms of inspecting the .mock property. You can do this manually yourself if that's more to your taste or if you have to do something more specific:
// if the mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);
// If the mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);
// If the last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);
// If the first arg of the last call to the mock function was `42`
// (note that there are no sugar helpers for this specific of an assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
// If a snapshot will check that a mock was invoked the same number of times,
// and in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');
Previous:
Jest Platform Packages.
Next:
Snapshot Testing with Jest.
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://w3resource.com/jest/mock-functions.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics