Mocking complex module using Jest.js - javascript

This is what the module I want to mock looks like:
class TheModule {
constructor() {
this.subClass = new SubClass();
}
}
class SubClass {
constructor() {
this.someMethod = () => 'Did Something great';
}
}
module.exports = TheModule;
This is TheModule usage I want to test:
const TheModule = require('./TheModule');
const method = () => {
const theModule = new TheModule();
return theModule.subClass.someMethod();
}
module.exports = method;
This is my test:
describe('method test', () => {
it('should return "Test pass"', () => {
jest.doMock('./TheModule');
const theModuleMock = require('./TheModule');
const method = require('./TheModuleUsage');
const mock = () => 'Test pass';
theModuleMock.subClass.someMethod.mockImplementation(() => mock);
expect(method()).toEqual('Test pass');
});
});
When I run this test I get TypeError: Cannot read property 'someMethod' of undefined
Is it possible to mock this module without changing TheModule implementation?

If you will export SubClass you can mock it without changing TheModule but in your case you should mock SubClass property in TheModule explicitly with factory for example:
describe('method test', () => {
it('should return "Test pass"', () => {
let mockedSomeMethod = jest.fn().mockImplementation(() => 'Test pass');
jest.doMock('./TheModule', () => {
// mock constructor
return jest.fn().mockImplementation(() => {
return { subClass: { someMethod: mockedSomeMethod } }
});
});
const method = require('./TheModuleUsage');
expect(method()).toEqual('Test pass');
});
});

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?

mocking private methods to test an exported method fails in jest

I'm having an actual class like this
my-file.js
const methodA = () => { return 'output-from-methodA'; }
const methodB = () => { const b = methodA(); b.c = "out-put bind"; return b; }
module.exports = {
methodB
}
my-file.test.js
const { methodA, methodB } = require('./my-file.js');
describe('methodB testing', () => {
it('should call methodA', () => {
methodB();
expect(methodA).toHaveBeenCalled()
}
});
here methodA is private method, so it is not explicit to the test file, then how i ensure it is called or not in the test files
There is no way to test the private function, only the alternative way found is to test the outputs like
const { methodA, methodB } = require('./my-file.js');
describe('methodB testing', () => {
it('should call methodA', () => {
const result = methodB();
expect(result.b.c).toBe("out-put bind")
}
});

Mock class using JEST in node

So, this is actually something that happened to my several times and I never knew how to solve it.
I have a class like this
//myClass.js
const { get } = require('../models/myModel');
class MyClass {
constructor(id) {
this.id = id;
}
async getByID() {
return get(this.id);
}
}
and a controller that is
//controller.js
module.exports = MyClass;
const MyClass = require('./MyClass.js');
module.exports = {
getController: (req, res, next) => {
try {
const MyObject = new MyClass(1);
const info = await MyObject.getByID();
res.send(info)
}
catch (e) {
next(e);
}
}
}
When I want to do an E2E test, i need to mock that getByID to see what happen when it resolves and rejects.
So, how do I mock a constructor? I did something like this
// myclass.spec.js
const MyClass = require('./MyClass.js');
jest.mock('./MyClass', () => jest.fn().mockImplementation(() => ({
getByID: Promise.resolve({id: 1, name: 'John'}),
})));
describe('Testing MyClass', () => {
it('Should return info', () => {
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
})
I know this test currently works, but now I cant change that getByID mock value to see what happen when it rejects the promise.
If I try to include it inside the test (it) .... it won't mock anything...
I want something like
const MyClass = require('./MyClass.js');
const {pipelineController} = require('./controllers.js')
jest.mock('./MyClass', () => jest.fn().mockImplementation(() => ({
getInfo: jest.fn(),
})));
describe('Testing MyClass', () => {
it('Should return info', () => {
ProcessService.getInfo.mockImplementation(() => Promise.resolve('the value i want'))
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
it('Should return info', () => {
ProcessService.getInfo.mockImplementation(() => Promise.reject('the reason i want'))
const res = httpMocks.createResponse();
const mReq = httpMocks.createRequest();
const mNext = jest.fn();
const mRes = mockResponse();
await pipelineController(mReq, mRes, mNext);
expect(mRes.send).toHaveBeenCalledWith(1);
done();
})
})
Since getByID is called right after instantiation, a spy should be available outside mocked class in order to change the implementation.
This can be done with class auto-mock that works in obscure and unspecified ways but may be suitable for this specific case:
jest.mock('./MyClass.js')
const MyClass = require('./MyClass.js');
...
MyClass.prototype.getByID.mockResolvedValue({id: 1, name: 'John'});
// instantiate MyClass and call getByID
And this can be done by exposing a spy outside the class:
const mockGetByID = jest.fn();
jest.mock('../../../services/ProcessService', () => jest.fn(() => (
{ getByID: mockGetByID }
)));
const MyClass = require('./MyClass.js');
...
mockGetByID.mockResolvedValue({id: 1, name: 'John'});
// instantiate MyClass and call getByID

How can I spy on an imported function in a grandparent class when testing a child class?

I'm having trouble spying on a function that is imported from a node module. I am testing a child class and the module is imported in a grandparent class, and I need to see what arguments the function is called with.
The code works as expected but I've tested with Jasmine's built in spyOn and also sinon.spy but neither spy is being called.
Code:
// ChildClass.js
const ParentClass = require('./ParentClass');
module.exports = class ChildClass extends ParentClass {
constructor () {
super();
this.foo();
}
foo () {
this.message = this.importGetter('Some input');
}
};
// ParentClass.js
const GrandparentClass = require('./GrandparentClass');
module.exports = class ParentClass extends GrandparentClass {
constructor () {
super();
this._message = null;
}
get message () {
return this._message;
}
set message (value) {
this._message = value;
}
};
// GrandparentClass.js
const nodeModuleFunction = require('./nodeModule').nodeModuleFunction;
module.exports = class GrandparentClass {
get importGetter () {
return nodeModuleFunction;
}
};
// nodeModule.js
async function nodeModuleFunction (input) {
console.error('Do something with input: ', input);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Returned from node module.');
}, 300);
});
}
exports.nodeModuleFunction = nodeModuleFunction;
Test code:
// test.spec.js
const ChildClass = require('./ChildClass');
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
describe('Jasmine spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
spyOn(nodeModule, 'nodeModuleFunction').and.callThrough();
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const spy = sinon.spy(nodeModule, 'nodeModuleFunction');
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
Test results:
Jasmine started
Do something with input: Some input
ChildClass test
Jasmine spy
✗ should call nodeModule.nodeModuleFunction with given value
- Expected spy nodeModuleFunction to have been called.
- Expected spy nodeModuleFunction to have been called with [ 'Some input' ] but it was never called.
Do something with input: Some input
Sinon spy
✗ should call nodeModule.nodeModuleFunction with given value
- Expected false to be true.
- Expected false to be true.
Editing with solution following Brian's suggestion:
const nodeModule = require('./nodeModule');
describe('ChildClass test', () => {
let ChildClass;
beforeAll(() => {
spyOn(nodeModule, 'nodeModuleFunction').and.callThrough(); // create the spy...
ChildClass = require('./ChildClass'); // ...and now require ChildClass
});
afterEach(() => {
nodeModule.nodeModuleFunction.calls.reset();
});
describe('Jasmine spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
it('should still call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
let spy;
let ChildClass;
beforeAll(() => {
spy = sinon.spy(nodeModule, 'nodeModuleFunction'); // create the spy...
ChildClass = require('./ChildClass'); // ...and now require ChildClass
});
afterEach(() => {
spy.resetHistory();
});
afterAll(() => {
spy.restore();
});
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
it('should still call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
GrandparentClass.js requires nodeModule.js and grabs a reference to nodeModuleFunction as soon as it runs...
...so you just need to make sure your spy is in place before it runs:
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const spy = sinon.spy(nodeModule, 'nodeModuleFunction'); // create the spy...
const ChildClass = require('./ChildClass'); // ...and now require ChildClass
const object = new ChildClass();
expect(object.message).not.toBeNull(); // Success!
expect(spy.called).toBe(true); // Success!
expect(spy.withArgs('Some input').calledOnce).toBe(true); // Success!
object.message.then(message => {
expect(message).toBe('Returned from node module.'); // Success!
done();
});
});
});
});
I updated the Sinon test, but the approach works for the Jasmine test as well.

Jest mock module multiple times with different values

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()
})
})

Categories