There is a function I need to unit test:
const readiness = require("./readiness");
function stopProcessNewRequests(){
readiness.setNotReady();
}
Here is my "readiness" file:
const state = require("./state");
const hooks = require("./hooks");
function setReady(){
state.ready = true;
hooks.notifyHook(state.ready);
}
function setNotReady(){
state.ready = false;
hooks.notifyHook(state.ready);
}
module.exports = {
setReady, setNotReady
};
and finally, the state.js file:
exports.ready = false;
exports.pendingRequests = {};
exports.changeStatusHooks = [];
exports.requestsInProcessNum = 0;
exports.authClient = null;
exports.webOperationsClient = null;
exports.webQueryClient = null;
As you can see, there are multiple chained imports, how do I mock them? I need my state file to be of certain values in order to check if it actually changes.
Here's what I have, but state does not seem to change, and the test fails.
describe('Testing processing new requests:', ()=> {
test('should stop processing new requests:', ()=> {
// jest.mock('../lib/grpc/readiness',);
jest.mock('../lib/grpc/state');
const state = require("../lib/grpc/state");
const { stopProcessNewRequests } = require('../lib/grpc/requestsManager');
state.ready = true;
stopProcessNewRequests();
expect(state.ready).toBeFalsy();
})
})
It is best not to test across modules. Which means for testing requestsManager module, you should mock readiness module, not indirectly module state and hooks. Besides, jest.mock should NOT be used in function scope, it should be used in module scope.
E.g.
readiness.js:
const state = require('./state');
const hooks = require('./hooks');
function setReady() {
state.ready = true;
hooks.notifyHook(state.ready);
}
function setNotReady() {
state.ready = false;
hooks.notifyHook(state.ready);
}
module.exports = {
setReady,
setNotReady,
};
hooks.js:
function notifyHook() {}
module.exports = { notifyHook };
requestsManager.js:
const readiness = require('./readiness');
function stopProcessNewRequests() {
readiness.setNotReady();
}
module.exports = { stopProcessNewRequests };
state.js:
exports.ready = false;
exports.pendingRequests = {};
exports.changeStatusHooks = [];
exports.requestsInProcessNum = 0;
exports.authClient = null;
exports.webOperationsClient = null;
exports.webQueryClient = null;
Below are test files:
requestsManager.test.js:
const { stopProcessNewRequests } = require('./requestsManager');
const readiness = require('./readiness');
describe('62057531', () => {
it('should pass', () => {
const setNotReadyMock = jest.spyOn(readiness, 'setNotReady').mockReturnValueOnce();
stopProcessNewRequests();
expect(setNotReadyMock).toBeCalledTimes(1);
});
});
readiness.test.js:
const { setNotReady, setReady } = require('./readiness');
const hooks = require('./hooks');
const state = require('./state');
describe('62057531', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should set state to not ready', () => {
jest.spyOn(hooks, 'notifyHook').mockReturnValueOnce();
setNotReady();
expect(hooks.notifyHook).toBeCalledWith(false);
expect(state.ready).toBeFalsy();
});
it('should set state to ready', () => {
jest.spyOn(hooks, 'notifyHook').mockReturnValueOnce();
setReady();
expect(hooks.notifyHook).toBeCalledWith(true);
expect(state.ready).toBeTruthy();
});
});
Unit test results with coverage report:
PASS stackoverflow/62057531/requestsManager.test.js (14.294s)
62057531
✓ should pass (6ms)
PASS stackoverflow/62057531/readiness.test.js
62057531
✓ should set state to not ready (5ms)
✓ should set state to ready (1ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 75 | 100 |
hooks.js | 100 | 100 | 0 | 100 |
readiness.js | 100 | 100 | 100 | 100 |
requestsManager.js | 100 | 100 | 100 | 100 |
state.js | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 17.288s, estimated 21s
Related
I'm trying to figure out how to perform unit tests, with Jest, on my code but I have been struggling with this. I want to be able to test the constructor and the method in my unit test but I can't understand how to do it in an efficient way.
class CommandOption {
constructor(commands) {
this.section = commands[0]
this.method = commands[1]
this.command1 = commands[2]
}
option(optionName) {
return require(`../commands/options/${optionName}`)(this)
}
}
I know that I can test the constructor fairly easily, in this case, but I don't know if it is good way or not.
const CommandOption = require('./command-option')
it('should equal "hardware", "get", and "byid"', () => {
let commandOption = new CommandOption(['hardware','get','byid'])
expect(commandOption.section).toBe('hardware')
expect(commandOption.method).toBe('get')
expect(commandOption.command1).toBe('byid')
}
I don't really know how to go about mocking the option method from there... I have read about using jest.spyOn() but I can't seem to wrap my head around it for my case... probably because I am trying to overthink it.
Unit test solution:
command-option.js:
class CommandOption {
constructor(commands) {
this.section = commands[0];
this.method = commands[1];
this.command1 = commands[2];
}
option(optionName) {
return require(`./commands/options/${optionName}`)(this);
}
}
module.exports = CommandOption;
command-option.test.js:
const CommandOption = require('./command-option');
describe('64327189', () => {
it('should equal "hardware", "get", and "byid"', () => {
let commandOption = new CommandOption(['hardware', 'get', 'byid']);
expect(commandOption.section).toBe('hardware');
expect(commandOption.method).toBe('get');
expect(commandOption.command1).toBe('byid');
});
it('should load option by name', () => {
const optionSetter = jest.fn();
jest.doMock('./commands/options/host', () => optionSetter);
let commandOption = new CommandOption(['hardware', 'get', 'byid']);
const optionName = 'host';
commandOption.option(optionName);
expect(optionSetter).toBeCalledWith(commandOption);
});
});
commands/options/host.js:
// whatever
unit test result with coverage report:
PASS src/stackoverflow/64327189/command-option.test.js
64327189
✓ should equal "hardware", "get", and "byid" (4ms)
✓ should load option by name (3ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
command-option.js | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.848s, estimated 10s
I have a function, lets call it generateName, which as you’ve guessed it, generates a name. The problem is that a new name is generated each time time a test is ran.
In one of my tests, I assert that a function is called with an object containing this name. However, the name keeps on changing. I could just check that the object has property name, but I don’t really want to do that.
My idea is that I can mock the return value of the generateName function and do something like this
Import { generateName } from ‘libs/generateName’
jest.fn(generateName).mockResolvedValue ( ‘hello’ )
expect ( spy ).toHaveBeenCalledWith (
expect.objectContaining ( {
name: 'houses',
} )
)
You can use jest.mock(moduleName, factory, options) to mock libs/generateName module.
E.g.
generateName.ts:
export async function generateName() {
const name = Math.random() + '';
return name;
}
main.ts:
import { generateName } from './generateName';
export function main() {
return generateName();
}
main.test.ts:
import { main } from './main';
import { generateName } from './generateName';
jest.mock('./generateName', () => {
return {
generateName: jest.fn(),
};
});
describe('61350152', () => {
it('should pass', async () => {
(generateName as jest.MockedFunction<typeof generateName>).mockResolvedValueOnce('hello');
const actual = await main();
expect(actual).toBe('hello');
});
});
unit test results with coverage report:
PASS stackoverflow/61350152/main.test.ts (28.524s)
61350152
✓ should pass (6ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
main.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 31.98s
I have a function that looks roughly like this:
import {someLibraryMethod} from 'someLibrary';
function setFullWidth(el) {
someLibraryMethod(el);
el.setAttribute('width', '100%');
}
I'm testing this function by using Sinon to create a mock element that only supports setAttribute:
const fakeElement = {
setAttribute: sinon.spy()
};
setFullWidth(fakeElement);
assert(fakeElement.setAttribute.calledWithExactly('width', '100%'));
The problem is that someLibraryMethod assumets el is an HTML element and tries to call some other methods on it, such as hasAttribute. I could add these as stubs or spies to fakeElement too, but I was wondering if Sinon has a way to create an object that will spy on any method or property access on it?
Something like:
const fakeElement = sinon.spyOnAnyPropety();
fakeElement.setAttribute(); // valid
fakeElement.hasAttribute(); // valid
fakeElement.foo(); // also valid
// some way to check which methods were called
You can use sinon.stub(obj) to
Stubs all the object’s methods.
E.g.
index.ts:
export function setFullWidth(el) {
el.setAttribute('width', '100%');
el.hasAttribute('width');
el.foo();
}
index.spec.ts:
import { setFullWidth } from './';
import sinon, { SinonStubbedInstance } from 'sinon';
import { expect } from 'chai';
describe('58868361', () => {
const fakeElement = {
setAttribute(attr, value) {
console.log('setAttribute');
},
hasAttribute(attr) {
console.log('hasAttribute');
},
foo() {
console.log('foo');
},
};
afterEach(() => {
sinon.restore();
});
it('should spy all methods of el', () => {
const logSpy = sinon.spy(console, 'log');
const stub: SinonStubbedInstance<typeof fakeElement> = sinon.stub(fakeElement);
setFullWidth(fakeElement);
expect(stub.setAttribute.calledWithExactly('width', '100%')).to.be.true;
expect(stub.hasAttribute.calledWithExactly('width')).to.be.true;
expect(stub.foo.calledOnce).to.be.true;
expect(logSpy.callCount).to.be.equal(0);
logSpy.restore();
});
it('should restore to original methods of el', () => {
const logSpy = sinon.spy(console, 'log');
setFullWidth(fakeElement);
expect(logSpy.callCount).to.be.equal(3);
});
});
Unit test result with 100% coverage:
58868361
✓ should spy all methods of el
setAttribute
hasAttribute
foo
✓ should restore to original methods of el
2 passing (13ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mongoose5.x-lab/tree/master/src/stackoverflow/58868361
I need to unit test the following method which takes one parameter. But I am unable to test.
Component Method:
resetAdd(qa: qaModel) {
const street = qa.children.find(temp => temp.token === 'street');
const zip = qa.children.find(temp => temp.token === 'zip');
const city = qa.children.find(temp => temp.token === 'city');
this.fGroup.controls[street.pathway].callReset();
this.fGroup.controls[zip.pathway].callReset();
this.fGroup.controls[city.pathway].callReset();
}
TestFile:
it('resetAdd Method is called', () => {
const param1= jest.fn();
expect(component.resetAdd).toHaveBeenCalledWith(param1);
});
I am not sure what's wrong and also please let me know what else test case I can write.
Here is the unit test solution, you can use jest.spyOn:
By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries.
That means when you can jest.spyOn to spy on a method of object without custom implementation, the original method of this object will be executed as usual. Beside, there is a spy on the method, so you can use assert of jest to check if it is executed or not. For your case, the method is callReset.
index.ts:
type qaModel = any;
export class SomeComponent {
private fGroup = {
controls: {
street: {
callReset() {}
},
zip: {
callReset() {}
},
city: {
callReset() {}
}
}
};
resetAdd(qa: qaModel) {
const street = qa.children.find(temp => temp.token === 'street');
const zip = qa.children.find(temp => temp.token === 'zip');
const city = qa.children.find(temp => temp.token === 'city');
this.fGroup.controls[street.pathway].callReset();
this.fGroup.controls[zip.pathway].callReset();
this.fGroup.controls[city.pathway].callReset();
}
}
index.spec.ts:
import { SomeComponent } from './';
describe('SomeComponent', () => {
it('should call resetAdd', () => {
const comp = new SomeComponent();
const streetCallResetSpy = jest.spyOn(comp['fGroup']['controls']['street'], 'callReset');
const zipCallResetSpy = jest.spyOn(comp['fGroup']['controls']['zip'], 'callReset');
const cityCallResetSpy = jest.spyOn(comp['fGroup']['controls']['city'], 'callReset');
const qaModel = {
children: [
{ token: 'street', pathway: 'street' },
{ token: 'zip', pathway: 'zip' },
{ token: 'city', pathway: 'city' }
]
};
comp.resetAdd(qaModel);
expect(streetCallResetSpy).toBeCalledTimes(1);
expect(zipCallResetSpy).toBeCalledTimes(1);
expect(cityCallResetSpy).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58818402/index.spec.ts
SomeComponent
✓ should call resetAdd (5ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.612s, estimated 8s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58818402
You are not calling resetAdd method in unit test, still expecting it should have been called.
Steps are :
Spy method which you want to be called,
call that method
then check for to have been called
it('resetAdd Method is called', () => {
const param: qaModel = null // define param of type which is expected. I dont know structure of qaModel, so for example put as null
spyOn(component, 'resetAdd') // spy first
component.resetAdd(param)
expect(component.resetAdd).toHaveBeenCalledWith(param);});
I try to figure out how to test this kind of method
// Let's say models === null when we instantiate
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb),
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
My coverage is still staying that I didn't test the if part... This is not really true because when I ask for the getModels I can test its value (so implicitly the if.
Any idea?
Here is the solution:
index.ts:
import * as models from './models';
import { MongoDb, Models } from './interfaces';
export class UserDataSource {
private models: Models | null = null;
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb)
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
}
Unit test, index.spec.ts:
import { UserDataSource } from './';
import { MongoDb } from './interfaces';
import * as models from './models';
describe('UserDataSource', () => {
const mockedMongodb: MongoDb = {};
describe('#initialize', () => {
it('should initlialize models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.initialize(mockedMongodb);
expect(userDataSource.getModels()).toEqual(expect.objectContaining({ users: expect.any(models.UserModel) }));
expect(actualValue).toBe(userDataSource);
});
it('should not initialize models', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.initialize(mockedMongodb);
expect(actualValue).toBe(userDataSource);
});
});
describe('#getModels', () => {
it('should get models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual(null);
});
it('should get models correctly and not null', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual([]);
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/52729002/index.spec.ts
UserDataSource
#initialize
✓ should initlialize models correctly (5ms)
✓ should not initialize models (7ms)
#getModels
✓ should get models correctly (1ms)
✓ should get models correctly and not null (1ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
52729002 | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
52729002/models | 100 | 100 | 100 | 100 | |
UserModel.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 6.157s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52729002