How to change mock implementation on a per single test basis? - javascript

I'd like to change the implementation of a mocked dependency on a per single test basis by extending the default mock's behaviour and reverting it back to the original implementation when the next test executes.
More briefly, this is what I'm trying to achieve:
Mock dependency
Change/extend mock implementation in a single test
Revert back to original mock when next test executes
I'm currently using Jest v21. Here is what a typical test would look like:
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);
export default myMockedModule;
// __tests__/myTest.js
import myMockedModule from '../myModule';
// Mock myModule
jest.mock('../myModule');
beforeEach(() => {
jest.clearAllMocks();
});
describe('MyTest', () => {
it('should test with default mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
// Extend change mock
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// Restore mock to original implementation with no side effects
});
it('should revert back to default myMockedModule mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
});
Here is what I've tried so far:
mockFn.mockImplementationOnce(fn)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myMockedModule.b.mockImplementationOnce(() => 'overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Reverts back to original implementation after first call
Cons
It breaks if the test calls b multiple times
It doesn't revert to original implementation until b is not called (leaking out in the next test)
jest.doMock(moduleName, factory, options)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
jest.doMock('../myModule', () => {
return {
a: jest.fn(() => true,
b: jest.fn(() => 'overridden',
}
});
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Explicitly re-mocks on every test
Cons
Cannot define default mock implementation for all tests
Cannot extend default implementation forcing to re-declare each mocked method
Manual mocking with setter methods (as explained here)
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
let a = true;
let b = true;
myMockedModule.a = jest.fn(() => a);
myMockedModule.b = jest.fn(() => b);
myMockedModule.__setA = (value) => { a = value };
myMockedModule.__setB = (value) => { b = value };
myMockedModule.__reset = () => {
a = true;
b = true;
};
export default myMockedModule;
// __tests__/myTest.js
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myModule.__setB('overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
myModule.__reset();
});
Pros
Full control over mocked results
Cons
Lot of boilerplate code
Hard to maintain on long term
jest.spyOn(object, methodName)
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
// Mock myModule
jest.mock('../myModule');
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// How to get back to original mocked value?
});
Cons
I can't revert mockImplementation back to the original mocked return value, therefore affecting the next tests

Use mockFn.mockImplementation(fn).
import { funcToMock } from './somewhere';
jest.mock('./somewhere');
beforeEach(() => {
funcToMock.mockImplementation(() => { /* default implementation */ });
// (funcToMock as jest.Mock)... in TS
});
test('case that needs a different implementation of funcToMock', () => {
funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
// (funcToMock as jest.Mock)... in TS
// ...
});

A nice pattern for writing tests is to create a setup factory function that returns the data you need for testing the current module.
Below is some sample code following your second example although allows the provision of default and override values in a reusable way.
const spyReturns = returnValue => jest.fn(() => returnValue);
describe("scenario", () => {
beforeEach(() => {
jest.resetModules();
});
const setup = (mockOverrides) => {
const mockedFunctions = {
a: spyReturns(true),
b: spyReturns(true),
...mockOverrides
}
jest.doMock('../myModule', () => mockedFunctions)
return {
mockedModule: require('../myModule')
}
}
it("should return true for module a", () => {
const { mockedModule } = setup();
expect(mockedModule.a()).toEqual(true)
});
it("should return override for module a", () => {
const EXPECTED_VALUE = "override"
const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
});
});
It's important to say that you must reset modules that have been cached using jest.resetModules(). This can be done in beforeEach or a similar teardown function.
See jest object documentation for more info: https://jestjs.io/docs/jest-object.

Little late to the party, but if someone else is having issues with this.
We use TypeScript, ES6 and babel for react-native development.
We usually mock external NPM modules in the root __mocks__ directory.
I wanted to override a specific function of a module in the Auth class of aws-amplify for a specific test.
import { Auth } from 'aws-amplify';
import GetJwtToken from './GetJwtToken';
...
it('When idToken should return "123"', async () => {
const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
getIdToken: () => ({
getJwtToken: () => '123',
}),
}));
const result = await GetJwtToken();
expect(result).toBe('123');
spy.mockRestore();
});
Gist:
https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2
Tutorial:
https://medium.com/p/b4ac52a005d#19c5

When mocking a single method (when it's required to leave the rest of a class/module implementation intact) I discovered the following approach to be helpful to reset any implementation tweaks from individual tests.
I found this approach to be the concisest one, with no need to jest.mock something at the beginning of the file etc. You need just the code you see below to mock MyClass.methodName. Another advantage is that by default spyOn keeps the original method implementation but also saves all the stats (# of calls, arguments, results etc.) to test against, and keeping the default implementation is a must in some cases. So you have the flexibility to keep the default implementation or to change it with a simple addition of .mockImplementation as mentioned in the code below.
The code is in Typescript with comments highlighting the difference for JS (the difference is in one line, to be precise). Tested with Jest 26.6.
describe('test set', () => {
let mockedFn: jest.SpyInstance<void>; // void is the return value of the mocked function, change as necessary
// For plain JS use just: let mockedFn;
beforeEach(() => {
mockedFn = jest.spyOn(MyClass.prototype, 'methodName');
// Use the following instead if you need not to just spy but also to replace the default method implementation:
// mockedFn = jest.spyOn(MyClass.prototype, 'methodName').mockImplementation(() => {/*custom implementation*/});
});
afterEach(() => {
// Reset to the original method implementation (non-mocked) and clear all the mock data
mockedFn.mockRestore();
});
it('does first thing', () => {
/* Test with the default mock implementation */
});
it('does second thing', () => {
mockedFn.mockImplementation(() => {/*custom implementation just for this test*/});
/* Test utilising this custom mock implementation. It is reset after the test. */
});
it('does third thing', () => {
/* Another test with the default mock implementation */
});
});

I did not manage to define the mock inside the test itself so I discover that I could mock several results for the same service mock like this :
jest.mock("#/services/ApiService", () => {
return {
apiService: {
get: jest.fn()
.mockResolvedValueOnce({response: {value:"Value", label:"Test"}})
.mockResolvedValueOnce(null),
}
};
});
I hope it'll help someone :)

It's a very cool way I've discovered on this blog https://mikeborozdin.com/post/changing-jest-mocks-between-tests/
import { sayHello } from './say-hello';
import * as config from './config';
jest.mock('./config', () => ({
__esModule: true,
CAPITALIZE: null
}));
describe('say-hello', () => {
test('Capitalizes name if config requires that', () => {
config.CAPITALIZE = true;
expect(sayHello('john')).toBe('Hi, John');
});
test('does not capitalize name if config does not require that', () => {
config.CAPITALIZE = false;
expect(sayHello('john')).toBe('Hi, john');
});
});

Related

How can I test a class which contains imported async methods in it?

This is my first time working with tests and I get the trick to test UI components. Now I am attempting to test a class which has some static methods in it. It contains parameters too.
See the class:
import UserInfoModel from '../models/UserInfo.model';
import ApiClient from './apiClient';
import ApiNormalizer from './apiNormalizer';
import Article from '../models/Article.model';
import Notification from '../models/Notification.model';
import Content from '../models/Link.model';
export interface ResponseData {
[key: string]: any;
}
export default class ApiService {
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then(res => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
static getProfile(callback: (a: UserInfoModel) => void) {
return ApiService.makeApiCall(`profile`, ApiNormalizer.normalizeProfile, callback);
}
}
I already created a small test which is passing but I am not really sure about what I am doing.
// #ts-ignore
import moxios from 'moxios';
import axios from 'axios';
import { baseURL } from './apiClient';
import { dummyUserInfo } from './../models/UserInfo.model';
describe('apiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('should perform get profile call', done => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
axiosInstance
.get(`${baseURL.DEV}profile`)
.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.data._user).toEqual(dummyUserInfo);
})
.finally(done);
});
});
I am using moxios to test the axios stuff -> https://github.com/axios/moxios
So which could be the proper way to test this class with its methods?
Introduction
Unit tests are automated tests written and run by software developers to ensure that a section of an application meets its design and behaves as intended. As if we are talking about object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. So if we consider your ApiService.makeApiCall function:
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then((res: any) => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
we can see that it has one external resource calling ApiClient.get which should be mocked. It's not entirely correct to mock the HTTP requests in this case because ApiService doesn't utilize them directly and in this case your unit becomes a bit more broad than it expected to be.
Mocking
Jest framework provides great mechanism of mocking and example of Omair Nabiel is correct. However, I prefer to not only stub a function with a predefined data but additionally to check that stubbed function was called an expected number of times (so use a real nature of mocks). So the full mock example would look as follows:
/**
* Importing `ApiClient` directly in order to reference it later
*/
import ApiClient from './apiClient';
/**
* Mocking `ApiClient` with some fake data provider
*/
const mockData = {};
jest.mock('./apiClient', function () {
return {
get: jest.fn((url: string) => {
return Promise.resolve({data: mockData});
})
}
});
This allows to add additional assertions to your test example:
it('should call api client method', () => {
ApiService.makeApiCall('test url', (data) => data, (res) => res);
/**
* Checking `ApiClient.get` to be called desired number of times
* with correct arguments
*/
expect(ApiClient.get).toBeCalledTimes(1);
expect(ApiClient.get).toBeCalledWith('test url');
});
Positive testing
So, as long as we figured out what and how to mock data let's find out what we should test. Good tests should cover two situations: Positive Testing - testing the system by giving the valid data and Negative Testing - testing the system by giving the Invalid data. In my humble opinion the third branch should be added - Boundary Testing - Test which focus on the boundary or limit conditions of the software being tested. Please, refer to this Glossary if you are interested in other types of tests.
The positive test flow flow for makeApiCall method should call normalizeCallback and callback methods consequently and we can write this test as follows (however, there is more than one way to skin a cat):
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledWith(firstCallback(mockData));
done();
});
});
Please, pay attention to several things in this test:
- I'm using done callback to let jest know the test was finished because of asynchronous nature of this test
- I'm using mockData variable which the data that ApiClient.get is mocked this so I check that callback got correct value
- mockData and similar variables should start from mock. Otherwise Jest will not allow to out it out of mock scope
Negative testing
The negative way for test looks pretty similar. ApiClient.get method should throw and error and ApiService should handle it and put into a console. Additionaly I'm checking that none of callbacks was called.
import ApiService from './api.service';
const mockError = {message: 'Smth Bad Happened'};
jest.mock('./apiClient', function () {
return {
get: jest.fn().mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
}
});
describe( 't1', () => {
it('should handle error', (done) => {
console.error = jest.fn();
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(mockError);
done();
});
});
});
Boundary testing
Boundary testing could be arguing in your case but as long as (according to your types definition normalizeCallback: (d: ResponseData) => ResponseData | null) first callback can return null it could be a good practice to check if is the successfully transferred to a second callback without any errors or exceptions. We can just rewrite our second test a bit:
it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return null;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
done();
});
});
Testing asynchronous code
Regarding testing asynchronous code you can read a comprehensive documentation here. The main idea is when you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest provides three ways how you can do this:
By means of a callback
it('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
Jest will wait until the done callback is called before finishing the test. If done() is never called, the test will fail, which is what you want to happen.
By means of promises
If your code uses promises, there is a simpler way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.
async/await syntax
You can use async and await in your tests. To write an async test, just use the async keyword in front of the function passed to test.
it('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
Example
Here you can find a ready to use example of your code
https://github.com/SergeyMell/jest-experiments
Please, let me know if something left unclear for you.
UPDATE (29.08.2019)
Regarding your question
Hi, what can I do to mock ./apiClient for success and error in the same file?
According to the documentation Jest will automatically hoist jest.mock calls to the top of the module (before any imports). It seems that you can do setMock or doMock instead, however, there are issues with mocking this way that developers face from time to time. They can be overridden by using require instead of import and other hacks (see this article) however I don't like this way.
The correct way for me in this case is do split mock defining and implementation, so you state that this module will be mocked like this
jest.mock('./apiClient', function () {
return {
get: jest.fn()
}
});
But the implementation of the mocking function differs depending on scope of tests:
describe('api service success flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
return Promise.resolve({data: mockData});
})
});
...
});
describe('api service error flow', () => {
beforeAll(() => {
//#ts-ignore
ApiClient.get.mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
});
...
});
This will allow you to store all the api service related flows in a single file which is what you expected as far as I understand.
I've updated my github example with api.spec.ts which implements all mentioned above. Please, take a look.
The unit test term is self-explanatory that you test a unit. A function in complete isolation. Any outside dependencies are mocked. Here if your'e testing makeApiCall function you'll have to stub it's parameters and then mock the ApiClient promise and expect the function to return whatever you're expecting it to return with respect to your mocked and stub parameters.
One thing that people normally forget and which is the most important is to test the negative cases of a function. What will happen if your function throws an error will it break the app. How your function behaves in case something fails. Tests are written to avoid breaking changes in the app.
here is a better guide how to test async functions in JEST which coding examples:
https://www.leighhalliday.com/mocking-axios-in-jest-testing-async-functions
Hope this helps
UPDATE
Mock your ApiClient
for pass case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.resolve(data)) // for pass case
})
for fail case:
jest.mock('./apiClient', () => {
get: jest.fn(() => Promise.reject(false)) // for fail case
})
now call your makeApiCall for both cases once for success and once for fail.
for fail case:
const makeCall = await makeApiCall( <your stub params here> )
expect(makeCall).toThrowError() // note here you can check whatever you have done to handle error. ToThrowError is not a built-in function but just for understanding
I've mostly done testing in Jasmine so this last piece of code is kind of a psuedo code.
I guess what you are asking is how to test ApiService. If this is the case, then mocking the very own thing you want to test would make the unit test pointless.
What I would expect is the following items
You just want to test logic in your own class, not in the library.
You don't want to make an actual network request, this spams the server and make the test slower to run.
If this is the case, then you should mock out some lib to control their behaviour and see how your class behave under those circumstances. And, mock out any operation that involves network IO, make your test faster and less reliant on external resources.
There are a few things you could check with some dependencies mocked out:
delegation, e.g. is axios called once, with the right param?
directly mock the behaviour of the lib, in your case using maxios.
import ApiService, { baseURL } from './apiClient';
describe('ApiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
// usually 1 test suite for each method
describe('#getProfile', (done) => {
// mocking behaviour
it('should perform get profile call', () => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
ApiService.getProfile((profile) => {
expect(profile).toEqual(dummyUserInfo); // you get what i mean
done();
});
});
// directly mock axios
it('delegates to axios', (done) => {
// you should put this to the top to avoid confusion, it will be hoisted
jest.mock('axios', () => ({
create: jest.fn(() => ({
get: jest.fn(() => Promise.resolve()),
})),
}));
ApiService.getProfile((profile) => {
// do some assertion
expect(axiosInstance.get).toHaveBeenCalledTimes(1);
expect(axiosInstance.get).toHaveBeenCalledWith(url, someParam, youGetIt);
done();
});
});
// rmb to test some error case
it('should throw when param is not correct', (done) => { ... });
});
});

Several mocks of one module in one file with jest

I have to functions for example a, b. Both of them are elements of one module, and they are re-exported in index.js. Function a invokes function b.
It all works if i use jest.mock on the top of the file, but if i want to specify different mock implementation of b function in every it block it doesn't work. Also i try'ed to use jest.doMock but it doesn't work as well.
a.js
import * as fromDependencies from '.'
export function(arg) {
return !fromDependencies && !arg;
}
b.js
export function b() {
//some code
return boolean;
}
index.js
export * from 'a.js',
export * from 'b.js'
testFile
import a from '../a.js';
describe('isGroupOverlaidTest', () => {
it('should return false', () => {
jest.mock('../.', () => ({
b: jest.fn(() => true);
}))
expect(a(true)).toBe(false);
});
it('should return true', function() {
jest.mock('../.', () => ({
b: jest.fn(() => false);
}))
expect(a(false)).toBe(false);
});
});
The results are fake, anyway i want to call my mocked function not the original one. When i have jest.mock in the top of the file it works but i can achieve just one mock for on file. Do mock does't work. I'll be really grateful if somebody can provide some example how i can resolve that ;).
You could use a spy instead of a mock. Since a exports a function instead of an object then you'd have to wrap it.
import a from '../a.js'
const aWrapped = { a }
describe('isGroupOverlaidTest', () => {
it('should return false', () => {
const mockA = jest.fn()
const spyedA jest.spyOn(aWrapped, a).mockReturnValue(mockA)
expect(spyedA(true)).toBe(false);
});
});
Something like that.

Jest mock class method on a test-by-test basis

I'm trying to mock out a utility library class with a method that returns a JSON.
Actual library structure
module.exports = class Common() {
getConfig() {
return {
real: 'data'
}
}
The file under test looks like:
const Common = require('./common');
const common = new Common();
const config = common.getConfig();
...
const someFunction = function() {
// config.real is used inside this function
}
I'm trying to mock out the Common class and return a different config JSON for each Jest test.
const fileUnderTest = require('./../fileUnderTest.js');
const Common = require('./../common.js');
jest.mock('./../common.js');
describe('something', () => {
it('test one', () => {
Common.getConfig = jest.fn().mockImplementation(() => {
return {
real : 'fake' // This should be returned for test one
};
});
fileUnderTest.someFunction(); //config.real is undefined at this point
});
it('test two', () => {
Common.getConfig = jest.fn().mockImplementation(() => {
return {
real : 'fake2' // This should be returned for test two
};
});
})
})
Is it possible to set the return value from the mock class method created by the automock of common.js at the top of the test file?
I've tried to use mockReturnValueOnce() etc.
jest.mock
In this case you don't really need to auto-mock the entire common module since you are just replacing the implementation of one method so jest.mock('./../common'); isn't necessary.
Common.getConfig
getConfig is a prototype method so getConfig exists on the prototype of Common. To mock it use Common.prototype.getConfig instead of Common.getConfig.
config in fileUnderTest.js
An instance of Common gets created and config gets set to the result of calling common.getConfig() as soon as fileUnderTest runs, which happens as soon as it gets required so the mock for Common.prototype.getConfig has to be in place before you call require('./../fileUnderTest').
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake' }));
const fileUnderTest = require('./../fileUnderTest');
describe('something', () => {
it('should test something', () => {
fileUnderTest.someFunction(); // config.real is 'fake' at this point
});
});
Update
To mock config.real differently for each test for code like this requires that the modules be reset between tests:
describe('something', () => {
afterEach(() => {
jest.resetModules(); // reset modules after each test
})
it('test one', () => {
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake' }));
const fileUnderTest = require('./../fileUnderTest');
fileUnderTest.someFunction(); // config.real is 'fake'
});
it('test two', () => {
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake2' }));
const fileUnderTest = require('./../fileUnderTest');
fileUnderTest.someFunction(); // config.real is 'fake2'
})
})
Resetting the modules is necessary because once a module is required it is added to the module cache and that same module gets returned each time it is required unless the modules are reset.

How To Reset Manual Mocks In Jest

I have a manual mock of crypto that looks like this:
// __mocks__/crypto.js
const crypto = jest.genMockFromModule('crypto')
const toString: Function = jest.fn(() => {
return {}.toString()
})
const mockStringable = {toString}
const update: Function = jest.fn(() => mockStringable)
const deciper = {update}
crypto.createDecipheriv = jest.fn(() => deciper)
export default crypto
Which is basically tested like this:
const crypto = require('crypto')
jest.mock('crypto')
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('decryptCookieValue', () => {
it('should call the crypto library correctly', () => {
const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
expect(crypto.createDecipheriv).toHaveBeenCalled()
// more tests, etc, etc, etc
expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
expect(result).toEqual({}.toString())
})
})
...
This works however if in that same test file, I test another method that invokes decryptCookieValue from within crypto.createDecipheriv no longer returns my mock decipher. Instead it returns undefined. For instance:
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('decryptCookieValue', () => {
it('should call the crypto library correctly', () => {
const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
expect(crypto.createDecipheriv).toHaveBeenCalled()
expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
expect(result).toEqual({}.toString())
})
})
...
...
describe('parseAuthenticationCookie', () => {
it('should create the correct object', () => {
// parseAuthenticationCookie calls decryptCookieValue internally
const result = parseAuthenticationCookie('', '') // Fails because internal call to crypto.createDecipheriv stops returning mock decipher.
expect(result).toEqual({accessToken: null})
})
})
})
I think this is an issue with resetting the manual mock because if I take that later test and move it into a file all by itself with the same surrounding test harness it works just fine.
// new test file
import crypto from 'crypto'
import { parseAuthenticationCookie } from './index'
jest.mock('crypto')
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('parseAuthenticationCookie', () => {
it('should create the correct object', () => {
// Works just fine now
const result = parseAuthenticationCookie('', '')
expect(result).toEqual({accessToken: null})
})
})
})
Is my assessment here correct and, if so, how do I reset the state of the manual mock after each test?
From Jest docs:
Does everything that mockFn.mockClear() does, and also removes any mocked return values or implementations.
ref: https://jestjs.io/docs/en/mock-function-api#mockfnmockreset
In your example you are assuming that calling resetAllMocks will set your manual mock back and it's not.
The reason why your test works in a separate file is because jest runs each file isolated, which is nice since you can screw up only the specs living in the same file.
In your particular case something that might work is calling jest.clearAllMocks() (since this will keep the implementation and returned values).
clearMocks options is also available at the jest config object (false as default), if you want to clear all your mocks on every test, this might be handy.
Hope this help you or anyone else having having a similar issue.
Bonus tip (no quite related) If you are mocking a module that it's being used internally by other module and in some specific test you want to mock that module again with a different mock, make sure to require the module that it's using the mocked module internally again in that specific test, otherwise that module will still reference the mock you specified next to the imports statements.
Looks like the better way to test this is something on the lines of:
jest.mock('crypto')
describe('decrypt()', () => {
afterEach(() => {
jest.resetAllMocks()
})
it('returns value', () => {
const crypto = require('crypto')
const encryptedValue = 'encrypted-value'
const update = jest.fn()
const pbkdf2SyncResult = 'test result'
crypto.pbkdf2Sync = jest.fn().mockImplementation(() => {
return pbkdf2SyncResult
})
crypto.createDecipheriv = jest.fn().mockImplementation((format, key, iv) => {
expect(format).toEqual('aes-256-cbc')
expect(key).toEqual(pbkdf2SyncResult)
expect(iv).toEqual(pbkdf2SyncResult)
return {update}
})
decrypt(encryptedValue)
const inputBuffer = Buffer.from(encryptedValue, 'base64')
expect(update).toHaveBeenCalledWith(inputBuffer)
})
})
This way I don't even have to have the manual mock and I can use mockImplementationOnce if I need to have the mock reset.

Jest: Change output of manual mock for different tests within a test suite

Let's say I have the following two files:
// index.js
...
import { IS_IOS } from 'common/constants/platform';
...
export const myFunction = () => (IS_IOS ? 'foo' : 'bar');
// index.test.js
...
import { myFunction } from './index';
jest.mock('common/constants/platform', () => ({ IS_IOS: true }));
describe('My test', () => {
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
// --> Here I want to change the value of IS_IOS to false
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
As you see my mocking function returns IS_IOS: true. I want it to return IS_IOS: false after my first test. How would I do that?
I also tried an adaptation of the solution here but I couldn't get it work, because there the mock returns a function:
module.exports = {
foo: jest.genMockFunction();
}
whereas my mock should return a boolean value which is not called inside the file I'm testing.
That's what I did here:
// common/constants/__mock__/platform
export const setIsIos = jest.fn(val => (IS_IOS = val));
export let IS_IOS;
// index.test.js
...
import { IS_IOS, setIsIos } from 'common/constants/platform';
jest.mock('common/constants/platform');
describe('My test', () => {
setIsIos('foo');
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
setIsIos('bar');
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
Oddly when console-logging, i.e. console.log(IS_IOS); I get the expected values. The test however seems to use the original value, i.e. undefined.
Add jest.resetModules() to the beforeEach() call of that describe() test suite:
describe('EventManager', () => {
beforeEach(() => {
jest.resetModules();
});
...
Additionally I found A more complete example on how to mock modules with jest here

Categories

Resources