Jest mock module multiple times with different values - javascript

I have a function that I want to test and this function uses an imported module:
var a = require('./a');
function add(b) {
return a + b;
}
module.exports = add;
That a module returns a number in this sample, but in my real project I use that as a config object that is changed from time to time manually.
var a = 1;
module.exports = a;
The test for the add function looks like this:
describe('add', () => {
it('should add the mock number 1 to 2', () => {
jest.setMock('./a', 1);
const add = require('./add');
expect(add(2)).toBe(3);
});
it('should add the mock number 2 to 2', () => {
jest.setMock('./a', 2);
const add = require('./add');
expect(add(2)).toBe(4);
});
});
First test passes, The second test fails because it inherits from the first mock. Is there any way to mock the a module multiple times?
I would like a solution that doesn't imply refactoring the add function and instead focus on mocking that module multiple times. (in my real project that is a config file)
You can play around with the code here: https://repl.it/#adyz/NocturnalBadComma

Add
beforeEach(() => {
jest.resetModules();
});
Final tests
describe('add', () => {
beforeEach(() => {
jest.resetModules();
});
it('should add the mock number 5 to 2', () => {
jest.setMock('./a', 5);
const add = require('./add');
expect(add(2)).toBe(7);
});
it('should add the mock number 2 to 2', () => {
jest.setMock('./a', 2);
const add = require('./add');
expect(add(2)).toBe(4);
});
});
Demo: https://repl.it/repls/TrustingBelatedProprietarysoftware

To add to #Gigi's solution, I created another example, using jest.mock:
In the file multiplier.ts, multiplier is the exported function we want to test:
// file: multiplier.ts
import {getNumber} from './get-number'
const multiplier = (num:number) => num * getNumber()
export {multiplier}
In the file get-number.ts, getNumber is the module we want to mock:
// file: get-number.ts
const getNumber = () => 2
export {getNumber}
Here is the test:
// file: multiplier.test.ts
// import { multiplier } from "./multiplier" // <-- this will not work
describe("[multiplier]", () => {
beforeEach(() => {
jest.resetModules()
})
it('should mock getNumber so that getNumber return 3', () => {
const mockReturn = 3
jest.mock( './get-number', () => (
{ getNumber: jest.fn(()=>mockReturn) }
))
const { multiplier } = require('./multiplier')
expect(multiplier(2)).toBe(6)
})
it('should mock getNumber so that getNumber return 4', () => {
const mockReturn = 4
jest.mock( './get-number', () => (
{ getNumber: jest.fn(()=>mockReturn) }
))
const { multiplier } = require('./multiplier')
expect(multiplier(2)).toBe(8)
})
it('should mock getNumber so that getNumber return 5', () => {
const mockReturn = 5
jest.mock( './get-number', () => (
{ getNumber: jest.fn(()=>mockReturn) }
))
const { multiplier } = require('./multiplier')
expect(multiplier(2)).toBe(10)
})
})
Note: for this to work, we need to use require to import multiplier.ts

For callback functions, working approach is-
const { validate } = require("../../controllers/..")
describe('Party Authentication', () => {
afterEach(() => { jest.resetModules(); });
it('should return success response', async () => {
let req = {}
req['headers'] = {}
req['body'] = {}
jest.mock('../../config/database', () => ({
execute: (param1, param2, param3, callback) => callback(null, [{ return: 1, party_guid: "20090911093921694613", policy_guid: '20090911093921422222' }])
}));
const data = await validate(req)
expect(data.status).toBe(true)
expect(data).toHaveProperty('insuredGuid')
expect(data.insuredGuid).toBeTruthy()
expect(data).toHaveProperty('policyGuidAuthentication')
expect(data.policyGuidAuthentication).toBeTruthy()
})
it('should return response with error code and message', async () => {
let req = {}
req['headers'] = {}
req['body'] = {}
jest.mock('../../config/database', () => ({
execute: (param1, param2, param3, callback) => callback(null, [{ return: 0, error_message: "Segurado não foi localizado com a Guid informada", error_code: 5684 }])
}));
const data = await validate(req)
console.log("datadatadatadatadata", data)
expect(data.status).toBe(false)
expect(data).toHaveProperty('error')
expect(data.error).toBeTruthy()
})
})

Related

Mock a function and used it as UUT with Jest in JS

I want to test one function from exported object in one describe and mock it in another function from the same exported object since the second function is using the first function.
// funcs.js
const func1 = () => {
return true;
};
const func2 = () => {
const bool = func1();
if (bool) {
// do something
} else {
// do else
}
};
export const funcs = {func1, func2};
// funcs.spec.js
const {funcs as uut} from './funcs';
describe('unit', () => {
describe('func 1', () => {
test('', () => {
const bool = uut.func1();
expect(bool).toBeTruthy();
});
});
describe('func 2', () => {
test('', () => {
jest.mock(uut.func1).mockReturnValue(false);
uut.func2();
// rest of test
});
});
});
I tried using jest.requireActual but did not work. What is the best approach for this if even possible?

Jest mocking a module with module pattern and having unique functionality on each test

I'm having trouble having a unique implementation of search from this module pattern setup I have (MyConnection.js) - here's similar to what I have:
// MyConnection.js
const MyConnection = () => {
const search = async (q) => {
//...some functionality
};
return {
search,
};
};
//JEST TESTS
const MyConnection = require('../MyConnection')
// this works - but it sets the search implementation
// for the whole test file I believe
jest.mock('../MyConnection', () => {
return jest.fn(()=>{
search: ()=> ['mocked', 'fn', 'results']
})
})
//the jest tests - I want different
// implementations of search in each test
describe('connection tests', ()=>{
it('test one', ()=>{
//Not sure if its something like this to set 'search' for each test? This doesn't work as is
MyConnection.search.mockImplementation((q)=>{`You searched ${q}`})
})
it('test two', ()=>{
MyConnection.search.mockImplementation((q)={q.length>32? 'a': 'b' }})
})
})
How can I get unique Jest mock implementations of that search function for each test?
You should make sure that mock MyConnection always returns the same connection object in your test file and the module you want to test.
MyConnection.js:
const MyConnection = () => {
const search = async (q) => {};
return {
search,
};
};
module.exports = MyConnection;
main.js:
const MyConnection = require('./MyConnection');
async function main(q) {
const conn = MyConnection();
return conn.search(q);
}
module.exports = main;
main.test.js:
const MyConnection = require('./MyConnection');
const main = require('./main');
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
const conn = { search: jest.fn() };
return jest.fn(() => conn);
});
const mConn = MyConnection();
describe('connection tests', () => {
it('test one', async () => {
mConn.search.mockImplementation((q) => {
return `You searched ${q}`;
});
const actual = await main('teresa teng');
expect(actual).toBe('You searched teresa teng');
});
it('test two', async () => {
mConn.search.mockImplementation((q) => {
return q.length > 32 ? 'a' : 'b';
});
const actual = await main('_');
expect(actual).toBe('b');
});
});
test result:
PASS examples/70132655/main.test.js (10.454 s)
connection tests
✓ test one (2 ms)
✓ test two (1 ms)
console.log
MyConnection module gets mocked
at examples/70132655/main.test.js:5:11
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.903 s
You can't mock it like this:
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
return jest.fn(() => { search: jest.fn() });
});
Why? Because every time you call the MyConnection() function in the test file or the file you want to test. It will return a new mock object({ search: jest.fn() }), the mock objects in file under test and test case are not same.

get new Instance for function each test

I have some tests in one file,
I check my reducer with some case
My code looks like this
my code
import axiosInstance from '~/utils/network';
const fetcher = axiosInstance();
const fetchMiddleware = () => {
switch (type) {
case 'LOGOUT':{
try {
await fetcher.get(API.GET.LOGOUT_OPERATION);
dispatch({ type: 'LOGOUT_SUCCESS' });
} catch (err) {
dispatch({ type: 'LOGOUT_FAIL' });
}
});
}
}
}
my test
import axiosInstance from '../../src/utils/network';
import configureStore from 'redux-mock-store';
const middlewares = [fetchMiddleware, thunk];
const mockStore = configureStore(middlewares);
const store = mockStore(getInitialReducerState());
jest.mock('../../src/utils/network', () => {
const axiosInstance = jest.fn().mockImplementation(() => {
return {
get: jest.fn().mockImplementation(() => {
return {
headers: {},
};
}),
};
}) as any;
axiosInstance.configure = jest.fn();
return axiosInstance;
});
describe('test LOGOUT', () => {
beforeEach(() => {
store.clearActions();
});
it('should test be success', async () => {
await store.dispatch({
type: 'LOGOUT',
payload: { userName: 'testUserName' },
});
expect(store.getActions()).toContainEqual({
type: 'LOGOUT_SUCCESS',
});
});
it('should test be fail', async () => {
(axiosInstance as jest.Mock).mockImplementation(() => {
return {
get: jest.fn().mockImplementation(() => {
throw new Error(' ');
}),
};
});
await store.dispatch({
type: 'LOGOUT',
payload: { userName: 'testUserName' },
});
expect(store.getActions()).toContainEqual({
type: 'LOGOUT_FAIL',
});
});
});
I want to test two scenarios: success & fail,
I mock the axiosInstance function.
But even I override the mock in the second test I get the first mock because my code loads axiosInstance only once.
what can I do?
You need to use jest.isolateModules
Let's say we have 2 files:
./lib.js - this is your ~/utils/network
./repro.js - this is your file with the code under test
./lib.js:
export default function lib() {
return () => 10;
}
./repro.js:
import lib from './lib';
const fnInstance = lib();
export const fn = () => {
return fnInstance();
};
And the ./repro.test.js:
function getRepro(libMock) {
let repro;
// Must use isolateModules because we need to require a new module everytime
jest.isolateModules(() => {
jest.mock('./lib', () => {
return {
default: libMock,
};
});
repro = require('./repro');
});
// If for some reason in the future the behavior will change and this assertion will fail
// We can do a workaround by returning a Promise and the `resolve` callback will be called with the Component in the `isolateModules` function
// Or we can also put the whole test function inside the `isolateModules` (less preferred)
expect(repro).toBeDefined();
return repro;
}
describe('', () => {
it('should return 1', () => {
const { fn } = getRepro(function lib() {
return () => 1
});
expect(fn()).toEqual(1);
});
it('should return 2', () => {
const { fn } = getRepro(function lib() {
return () => 2
});
expect(fn()).toEqual(2);
});
});
It's preferable to use existing library to mock Axios, it saves from boilerplate code and potential mistakes in mock implementation; moxios has been already suggested.
It's inconvenient to mock axiosInstance per test because it has been already called on the import of tested module, so this requires it to be re-imported per test; another answer explains how it's done with jest.isolateModules.
Since axiosInstance is evaluated only once and is supposed to return mocked object, it's convenient to mock it once per test and then change implementations:
jest.mock('~/utils/network', () => {
const axiosMock = { get: jest.fn(), ... };
return {
axiosInstance: () => axiosMock;
};
});
const axiosMock = axiosInstance();
...
(axiosMock.get axiosInstance as jest.Mock).mockImplementation(() => {
throw new Error(' ');
});
await store.dispatch(...);
This requires to use jest.restoreAllMocks in beforeEach or similar Jest configuration option to avoid test cross-contamination.
Notice that Axios doesn't throw errors but rather return rejected promises, this may affect test results, see the note regarding the benefits of libraries.

Restore implementation after mocking jest

I'm trying to mock a function and after testing use the real implementation for other tests.
const myService = () => {
return {
foo: () => return 1;
}
}
const myService = require('./myService.js');
jest.mock('./myService');
describe('testing myService', () => {
it('should return 2 by mocked', () => {
myService.mockImplementation(() => ({
foo: jest.fn().mockResolvedValueOnce(2),
}));
expect.assertions(1);
expect(myService().foo()).toBe(2);
jest.restoreAllMocks();
});
it('should return 1', () => {
expect(myService().foo()).toBe(1);
});
});
But after this, foo() is still mocked.
jest.spyOn(object, methodName) is a better choice to create a mock method for an object.
We can restore the method to original implementation by using jest.restoreAllMocks() inside afterEach() hook.
Beware that jest.restoreAllMocks() only works when the mock was created with jest.spyOn; other mocks will require you to manually restore them.
E.g.
myService.js:
const myService = () => {
return {
foo: () => 1,
};
};
module.exports = myService;
myService.test.js:
const myService = require('./myService.js');
describe('testing myService', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should return 2 by mocked', () => {
const service = myService();
jest.spyOn(service, 'foo').mockReturnValueOnce(2);
expect(service.foo()).toBe(2);
expect(service.foo).toBeCalledTimes(1);
});
it('should return 1', () => {
const service = myService();
expect(jest.isMockFunction(service.foo)).toBeFalsy();
expect(service.foo()).toBe(1);
});
});
test result:
PASS examples/68459394/myService.test.js (8.465 s)
testing myService
✓ should return 2 by mocked (3 ms)
✓ should return 1
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.548 s

Pass parameter to jest.mock

I have a mock as follows
jest.mock('react-hook-inview', () => {
const setRef = jest.fn();
const observer = jest.fn();
const intersecting = true;
const entry = {
y: 0,
};
return [setRef, intersecting, entry, observer];
});
Here I would like to change the intersecting value. How can I change that from one test to another? I was trying to use something like a factory:
const inView = (intersecting) => {
jest.mock('react-hook-inview', () => {
const setRef = jest.fn();
const observer = jest.fn();
const entry = {
y: 0,
};
return [setRef, intersecting, entry, observer];
});
}
and use it like
it('is invisible by default', () => {
const text = 'Test';
const { container } = render(<Reveal>{text}</Reveal>);
inView(false);
expect(container.firstChild).not.toBeVisible();
});
it('is visible when in view', () => {
const text = 'Test';
const { container } = render(<Reveal>{text}</Reveal>);
inView(true);
expect(container.firstChild).toBeVisible();
});
however this throws
The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: intersecting
does anybody have an idea?
cheers!
EDIT:
My solution for now is to mock it like this
import ReactHookInview from 'react-hook-inview';
jest.mock('react-hook-inview', () => ({
useInView: jest.fn().mockImplementation(() => {
const setRef = jest.fn();
const observer = jest.fn();
const intersecting = false;
const entry = {
boundingClientRect: {
y: 0,
},
};
return [setRef, intersecting, entry, observer];
}),
}));
and in my test I overwrite like this:
ReactHookInview.useInView.mockImplementation(() => {
const setRef = jest.fn();
const observer = jest.fn();
const intersecting = true;
const entry = {
boundingClientRect: {
y: 1,
},
};
return [setRef, intersecting, entry, observer];
});
but that's not really beautiful
You can mock your library as you have already done, import it and explicitly set it's value. And then before have two different sets of tests:
jest.mock('react-hook-inview', /* as above */ );
import rhi from 'react-hook-inview';
describe('my implementation', () => {
describe('while intersecting', () => {
let result;
beforeAll(() => {
rhi[1] = true;
result = myImplementationUsingIntersecting();
});
it('should result X', () => {
expect(result).toEqual(X);
});
});
describe('while NOT intersecting', () => {
let result;
beforeAll(() => {
rhi[1] = false;
result = myImplementationUsingIntersecting();
});
it.todo('should result Y');
});
})
working example
Edit 2: in order to properly mock a React hook
since the react hooks are functions that return stuff you'll can mock it like this
jest.mock('react-hook-inview', () => jest.fn());
// and you can take it's reference with import
import useInView from 'react-hook-inview';
// and then you can mock it's return value as array
beforeAll(() => {
useInView.mockReturnValue(['a', true, 'b', 'c']);
result = myImplementationUsingIntersecting();
})

Categories