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"
Related
I'm assigning values to this variables inside ngOnInit:
this.simStatsList$ = this.sideMenuService.getSimStatsList();
this.currentStation$ = this.simStatsList$.pipe(
map(station => station.find((station: ISimStats) => station.stationName === this.authService.userStation)),
) as Observable<ISimStats>;
This is my test:
it('should select userStation as currentStation', () => {
component.currentStation$.subscribe((response) => {
expect(response).toEqual(
{ stationName: 'test', stats: { open: 0, down: 0, preflight: 0 } }
);
});
});
It passes but is not covering the map function from rxjs. Also im providing sideMenuService and AuthService as mocked values and this is my mock. I'm missing something but i don't know what is it.
export const mockSideMenuService = {
getSimStatsList: () =>
of([
{ stationName: 'test', stats: { open: 0, down: 0, preflight: 0 } },
{ stationName: 'test1', stats: { open: 1, down: 1, preflight: 1 } }
] as ISimStats[])
}
export const mockAuthService = {
userStation: 'test'
}
Could you help me to cover the whole code?
After #will alexander comment i did some change and it worked:
First, pass the function to the sideMenuService and recieve needed data as parameters:
side-menu.service.ts
getCurrentSimStats(
simStatsList$: Observable<ISimStats[]>,
currentStation: string): Observable<ISimStats> {
return simStatsList$.pipe(
map((station) => station.find((station: ISimStats) => station.stationName === currentStation))) as Observable<ISimStats>;
}
Then my component test coverage passed as 100% but the sideMenuService wasn't so i wrote this small test on service spec file:
side-menu.service.spec.ts
it('should getCurrentStation', () =>{
service.getCurrentSimStats(of(mockSideMenuService.mockSimStatsResponse), 'test').subscribe((res) => {
expect(res).toEqual(mockSideMenuService.mockCurrentSimStatsResponse);
});
})
After this, everything worked and tests are passing!
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
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
I'm currently trying to cover 100% of my function but I'm facing a branch not covered and it don't understand why it's not covered and even how to fix and cover it.
test coverage result here
It tried many different tests but it didn't changed anything. I don't if the problems comes from me or Jest ?
My function
export const removeProductInList = (state: IItems, action: IBasketRemoveProductAction) => {
const {
payload: { uuid },
} = action;
const { [uuid]: itemToRemove, ...restOfItems } = state;
return restOfItems;
};
My tests
product1 = { id: 1 };
product2 = { id: 2 };
mockUuid1 = 'IdTest';
mockUuid2 = 'IdTest2';
mockItem1 = { product: product1, quantity: 1 };
mockItem2 = { product: product2, quantity: 1 };
mockListItems = {
[mockUuid1]: mockItem1,
[mockUuid2]: mockItem2,
};
it('should handle PRODUCT__REMOVE', () => {
expect(removeProductInList({ [mockUuid1]: mockItem1 }, removeProduct(mockUuid1))).toEqual({});
expect(removeProductInList(mockListItems, removeProduct(mockUuid1))).toEqual({ [mockUuid2]: mockItem2 });
expect(removeProductInList({}, removeProduct('acac'))).toEqual({});
});
I expect to cover all the function with my actual test.
My version of jest is 23.4.1, just in case.
You just have to specify the target esnext in your tsConfig file in ts-jest.
// jest.config.js
module.exports = {
...
'ts-jest': {
....
tsConfig: './tsconfig.json'),
},
// tsconfig.json
{
...,
"target": "esnext",
}
Try to test the removeProductList with extra properties
expect(removeProductInList({ [mockUuid1]: mockItem1, extraProp: 'someValue' }, removeProduct(mockUuid1))).toEqual({});
In my case, I followed Nicolas' answer and edited babel.config.json as below and it worked. Thank you, Nicolas.
{
"presets": ["#babel/preset-env"],
"targets": {"esmodules": true} <-- added this line
}
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
}