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
Related
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
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).
Below is a very simple jest unit test and when running it, you will get error like
Cannot spyOn on a primitive value; undefined given
TypeError: Cannot read property 'getItem' of undefined
but according to the last two comments of this post, localStorage and sessionStorage were already added to latest JSDOM and jest. If using jest-localstorage-mock and add it to my jest setupFiles then you will see weird error like
TypeError: object[methodName].mockImplementation is not a function
So my question is what's the best way to mock localStorage/sessionStorage in jest. Thanks
describe('window.sessionStorage', () => {
let mockSessionStorage;
beforeEach(() => {
mockSessionStorage = {};
jest.spyOn(window.sessionStorage, "getItem").mockImplementation(key => {
return mockSessionStorage[key];
});
});
describe('getItem-', () => {
beforeEach(() => {
mockSessionStorage = {
foo: 'bar',
}
});
it('gets string item', () => {
const ret = window.sessionStorage.getItem('foo');
expect(ret).toBe('bar');
});
});
});
Below is my jest config
module.exports = {
verbose: true,
//setupFiles: ["jest-localstorage-mock"],
testURL: "http://localhost/"
};
Here is the solution only use jestjs and typescript, nothing more.
index.ts:
export function getUserInfo() {
const userInfo = window.sessionStorage.getItem('userInfo');
if (userInfo) {
return JSON.parse(userInfo);
}
return {};
}
index.spec.ts:
import { getUserInfo } from './';
const localStorageMock = (() => {
let store = {};
return {
getItem(key) {
return store[key] || null;
},
setItem(key, value) {
store[key] = value.toString();
},
removeItem(key) {
delete store[key];
},
clear() {
store = {};
}
};
})();
Object.defineProperty(window, 'sessionStorage', {
value: localStorageMock
});
describe('getUserInfo', () => {
beforeEach(() => {
window.sessionStorage.clear();
jest.restoreAllMocks();
});
it('should get user info from session storage', () => {
const getItemSpy = jest.spyOn(window.sessionStorage, 'getItem');
window.sessionStorage.setItem('userInfo', JSON.stringify({ userId: 1, userEmail: 'example#gmail.com' }));
const actualValue = getUserInfo();
expect(actualValue).toEqual({ userId: 1, userEmail: 'example#gmail.com' });
expect(getItemSpy).toBeCalledWith('userInfo');
});
it('should get empty object if no user info in session storage', () => {
const getItemSpy = jest.spyOn(window.sessionStorage, 'getItem');
const actualValue = getUserInfo();
expect(actualValue).toEqual({});
expect(window.sessionStorage.getItem).toBeCalledWith('userInfo');
expect(getItemSpy).toBeCalledWith('userInfo');
});
});
Unit test result with 100% coverage report:
PASS src/stackoverflow/51566816/index.spec.ts
getUserInfo
✓ should get user info from session storage (6ms)
✓ should get empty object if no user info in session storage (1ms)
----------|----------|----------|----------|----------|-------------------|
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: 2 passed, 2 total
Snapshots: 0 total
Time: 4.548s, estimated 6s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/51566816
You probably do not even need a mock. Just use window.sessionStorage as usual and write your condition based on window.sessionStorage.getItem(...) result instead of spying window.sessionStorage.setItem . Simply don't forget to call window.sessionStorage.clear() in beforeEach as demonstrated.
From Eric Burel's comment
This worked for me in the context of using jest:
beforeAll(() =>
sessionStorage.setItem(
KeyStorage.KEY_NAME,
JSON.stringify([Permission.VALUE_])
)
);
afterAll(() =>
sessionStorage.removeItem(KeyStorage.CONTEXT_TYPE_GLOBAL_PERMISSIONS)
);
This works for me along with adding object:
defineProperty(window, 'sessionStorage', {
writable: true,
configurable: true,
value: localStorageMock
}