I am currently writing unit tests to improve my coverage and I am stuck on a function where I want to set the input parameter of a function.
The function I want to test is:
this.map.forEachFeatureAtPixel(e.pixel, (event: any) => {
if (
event.values_.name === 'name'
) {
this.openWeatherData(event.values_.name);
} //more logic ...
});
I want to test the code inside the callback function to test if the correct calls are made.
But how do I set the event parameter to something like
{ values_ : { name: 'name' } }
and execute the actual callback implementation to improve my coverage.
Use jest.spyOn(object, methodName) to create a mock version for this.map.forEachFeatureAtPixel() method and its mocked implementation. The mocked implementation accept two paramters: pixel and callback. Invoke the callback function manually with your mocked event object.
E.g.
index.ts:
export const obj = {
map: {
// simulate real implementation
forEachFeatureAtPixel(pixel, callback) {
callback();
},
},
method(e) {
this.map.forEachFeatureAtPixel(e.pixel, (event: any) => {
if (event.values_.name === 'name') {
console.log('openWeatherData');
}
});
},
};
index.test.ts:
import { obj } from './';
describe('67818053', () => {
it('should pass', () => {
const mEvent = { values_: { name: 'name' } };
const forEachFeatureAtPixelSpy = jest
.spyOn(obj.map, 'forEachFeatureAtPixel')
.mockImplementationOnce((pixel, callback) => {
callback(mEvent);
});
obj.method({ pixel: '12' });
expect(forEachFeatureAtPixelSpy).toBeCalledWith('12', expect.any(Function));
forEachFeatureAtPixelSpy.mockRestore();
});
});
test result:
PASS examples/67818053/index.test.ts (8.297 s)
67818053
✓ should pass (14 ms)
console.log
openWeatherData
at examples/67818053/index.ts:11:17
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.055 s
Related
Consider the following function:
let dictionary = {
there: "there"
}
function sayHi(word){
if (dictionary.hasOwnProperty(word)){
return "hello " + dictionary[word]
}
}
If I wanted to test the sayHi function, how would I mock the dictionary variable in a Jest test?
I've tried importing everything from the module and overwriting the dictionary object but that hasn't worked, likewise I've tried mocking it as a function, but still can't get it to work.
You can use rewire package to override variables within the module.
E.g.
index.js:
let dictionary = {
there: 'there',
};
function sayHi(word) {
if (dictionary.hasOwnProperty(word)) {
return 'hello ' + dictionary[word];
}
}
module.exports = { sayHi };
index.test.js:
const rewire = require('rewire');
describe('67044925', () => {
it('should pass', () => {
const mod = rewire('./');
mod.__set__('dictionary', { there: 'teresa teng' });
const actual = mod.sayHi('there');
expect(actual).toEqual('hello teresa teng');
});
it('should pass too', () => {
const mod = rewire('./');
mod.__set__('dictionary', { there: 'slideshowp2' });
const actual = mod.sayHi('there');
expect(actual).toEqual('hello slideshowp2');
});
});
unit test result:
PASS examples/67044925/index.test.js (12.148 s)
67044925
✓ should pass (15 ms)
✓ should pass too (4 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 14.047 s
settings.js
export default {
web: {
a: 1
},
mobile: {
b: 2
}
};
getSetting.js
import settings from "./settings";
export const getSetting = platform => {
return settings[platform];
};
getSettings.test.js
import { getSetting } from "./getSetting";
const TEST_SETTINGS = { c: 3 };
jest.mock("./settings", () => {
return {
test: TEST_SETTINGS
};
});
test("getSetting", () => {
expect(getSetting("test")).toEqual(TEST_SETTINGS);
});
Error
ReferenceError: Cannot access 'TEST_SETTINGS' before initialization
I believe this has something to do with hoisting. Is there a way to overcome this issue? Does jest provide any other means to achieve this?
I don't want to do this. This is not good when the mock data is large and used in multiple tests.
jest.mock("./settings", () => {
return {
test: { c: 3 }
};
});
expect(getSetting("test")).toEqual({ c: 3 });
jest.mock is automatically hoisted, this results in evaluating mocked module before TEST_SETTINGS is declared.
Also, this results in ./settings being mocked with test named export, while it's expected to have default export.
It shouldn't use temporary variable, the value is available when it's being imported:
import settings from "./settings";
jest.mock("./settings", () => {
return { default: {
test: { c: 3 }
} };
});
...
expect(getSetting("test")).toBe(settings.test);
Or, use Dynamic Imports import().
E.g.
const TEST_SETTINGS = { c: 3 };
jest.mock('./settings', () => {
return {
test: TEST_SETTINGS,
};
});
test('getSetting', async () => {
const { getSetting } = await import('./getSetting');
expect(getSetting('test')).toEqual(TEST_SETTINGS);
});
test result:
PASS examples/61843762/getSettings.test.js
✓ getSetting (4 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.25 s, estimated 4 s
jestjs version: "jest": "^26.6.3"
I tried writing test cases wrapped inside a class so that calling a method will execute the test case. But the url value will be initialized inside the beforeAll/ beforeEach block due to dependency factor. In that case i am not getting the url value, which results the failure of the test case execution.I could not pass url as an argument as well(url will only be initialized in beforeAll block). Is there any alternative soultion available to overcome this issue?
sampleTest.ts
export interface TestCaseArgumentsType {
baseUrl: string;
url: string;
}
export class Sample {
set args(value: TestCaseArgumentsType) {
this.arguments = value;
}
private arguments!: TestCaseArgumentsType;
sampleTestFunction() {
console.log(this.arguments.url); // **expected**: sampleUrl **actual**: cannot set property url of undefined
it('to check the before each execution effects in the test case', () => {
console.log(this.arguments.url); // sampleUrl
});
}
}
sampleTestSuite.test.ts
import { Sample, TestCaseArgumentsType } from './sampleTest';
describe('User Route', () => {
let sample = new Sample();
// Test suite
describe('GET Request', () => {
// Preparing Test Suite
beforeAll(async () => {
sample.args = <TestCaseArgumentsType>{ url: `sampleUrl` };
}, 20000);
// Executing
sample.sampleTestFunction();
});
});
The reason is the execution order of the code.
The key point is beforeAll function is executed before calling it, not before calling sample.sampleTestFunction(). So, when you call sample.sampleTestFunction() method, the statement sample.args = <TestCaseArgumentsType>{ url: 'sampleUrl' } inside the beforeAll function will not execute. The arguments property of sample is undefined. That's why you got error:
TypeError: Cannot read property 'url' of undefined
When jestjs test runner prepare to calling it, then, test runner will call beforeAll function firstly
sampleTest.ts:
export interface TestCaseArgumentsType {
baseUrl: string;
url: string;
}
export class Sample {
set args(value: TestCaseArgumentsType) {
this.arguments = value;
}
private arguments!: TestCaseArgumentsType;
sampleTestFunction() {
console.log('===execute 2===');
console.log(this.arguments.url); // another sampleUrl
it('to check the before each execution effects in the test case', () => {
console.log('===execute 4===');
console.log(this.arguments.url); // sampleUrl
});
}
}
sampleTestSuite.test.ts:
import { Sample, TestCaseArgumentsType } from './sampleTest';
describe('User Route', () => {
let sample = new Sample();
describe('GET Request', () => {
console.log('===execute 1===');
sample.args = <TestCaseArgumentsType>{ url: `another sampleUrl` };
beforeAll(async () => {
console.log('===execute 3===');
sample.args = <TestCaseArgumentsType>{ url: `sampleUrl` };
}, 20000);
sample.sampleTestFunction();
});
});
Unit test result:
PASS src/stackoverflow/58480169/sampleTestSuite.test.ts (7.092s)
User Route
GET Request
✓ to check the before each execution effects in the test case (3ms)
console.log src/stackoverflow/58480169/sampleTestSuite.test.ts:8
===execute 1===
console.log src/stackoverflow/58480169/sampleTest.ts:11
===execute 2===
console.log src/stackoverflow/58480169/sampleTest.ts:12
another sampleUrl
console.log src/stackoverflow/58480169/sampleTestSuite.test.ts:11
===execute 3===
console.log src/stackoverflow/58480169/sampleTest.ts:15
===execute 4===
console.log src/stackoverflow/58480169/sampleTest.ts:16
sampleUrl
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.89s
As you can see, I put some console.log to indicate the execution order of the code. The execution order is 1,2,3,4. Sorry for my English.
I have 2 functions where one calls the other and the other returns something, but I cannot get the test to work.
Using expect(x).toHaveBeenCalledWith(someParams); expects a spy to be used, but I am unaware of how to spy on a function within the same file...
Error: : Expected a spy, but got Function.
Usage: expect().toHaveBeenCalledWith(...arguments)
Example.ts
doSomething(word: string) {
if (word === 'hello') {
return this.somethingElse(1);
}
return;
}
somethingElse(num: number) {
var x = { "num": num };
return x;
}
Example.spec.ts
fake = {"num": "1"};
it('should call somethingElse', () => {
component.doSomething('hello');
expect(component.somethingElse).toHaveBeenCalledWith(1);
});
it('should return object', () => {
expect(component.somethingElse(1)).toEqual(fake);
});
In your Example.spec.ts, just add a spyOn(component, 'somethingElse'); as first line of your it('should call somethingElse ... test :
fake = {"num": "1"};
it('should call somethingElse', () => {
// Add the line below.
spyOn(component, 'somethingElse');
component.doSomething('hello');
expect(component.somethingElse).toHaveBeenCalledWith(1);
});
it('should return object', () => {
expect(component.somethingElse(1)).toEqual(fake);
});
The expect method needs a Spy as parameter when used before a toHaveBeenCalledWith (as per the Jasmine documentation).
Code :
Model = {};
var functionToTest = function (dataPoints) {
return Model.getDataPointValue(dataPoints, function (dataPoint) {
if (dataPoint.unit === 'unit') {
return dataPoint.value;
}
});
};
Model.functionToTest = functionToTest;
Test Case :
describe ('functionToTest', function () {
it ('validates correct value is returned', function () {
var dataPoints = [{
'unit': 'unit',
'value' : 1
}];
var mock = sinon.mock(Model);
mock.expects('getDataPointValue').once().withExactArgs(dataPoints, function (dataPoint) {
if (dataPoint.unit === 'unit') {
return dataPoint.value;
}
});
Model.functionToTest(dataPoints);
mock.verify();
});
});
Response :
ExpectationError: Unexpected call: getDataPointValue([{ unit: "unit", value: 1 }], function () {})
Expected getDataPointValue([{ unit: "unit", value: 1 }], function () {}) once (never called)
I cant figure out why it wouldnt accept the same function definition as a parameter. Any clues ?
Is there a different way to test function parameters being functions in mocha using sinon ?
If you are always passing the same function to the getDataPointValue, there's no need to test if that function was passed, you can test only if the first argument was passed like this:
mock.expects('getDataPointValue').once().withArgs(dataPoints);