how to spy on window.location functions? - javascript

i need to cover my code with some unit tests and in one of then i have the following situation.
app.tsx
async someMethod(
.
.
.
window.location.replace(sessionStorage.getItem(REDIRECT_VALUE));
.
.
.
)
and in my test file
window.location.replace = jest.fn();
.
.
somevariable.SomeMethod = jest.fn();
expect(window.location.replace).toHaveBeenCalledWith("some url to redirect on");
i'm getting the followein error : Cannot assign to read only property 'replace' of object '[object Location]'
i've tried other aproachs like
backupState = window.location
delete window.location;
window.location = Object.assign(new URL("https://example.org"), {
ancestorOrigins: "",
replace: jest.fn()
});
});
but i get different erros for each one of them, is there another way of doing it?
Previously i was using :
history.location.push(sessionStorage.getItem(REDIRECT_VALUE));
and
expect(auth.history.push).toHaveBeenCalled();
and in that case the test went OK.

One way to test it is:
Create a mock function to location.replace:
const replaceMock = jest.fn();
And spyOn window, replacing location object and setting the replaceMock to replace.
Complete test example:
const replaceMock = jest.fn();
describe('replace Location test ', () => {
it('should call location with specific arg', () => {
jest.spyOn(global as any, 'window', 'get').mockImplementationOnce(() => ({
location: {
replace: replaceMock,
},
}));
someMethod();
expect(replaceMock).toBeCalledWith('XX Value you want to test XX');
});
});

Related

Mocked function get triggered but toHaveBeenCalledWith do not recognize it

I mocked the useNavigation() hook from react-naitive/navigation but jest do not recognize it.
Jest gives me an error that the function did not get called, but when I log the function it got to be triggered.
I already tried it with wrapping they expect with waitFor but then I just get a typescript catch error
Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'catch')]
My jest test:
jest.mock('#react-navigation/native', () => {
const actualNav = jest.requireActual('#react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
// when I do () => { console.log('trigger') } it will be called
navigate: jest.fn(),
}),
useRoute: () => ({
params: { email: '' },
}),
};
});
....
it.only('should navigate to resend screen when button is pressed', async () => {
const { getByTestId } = render(
withStoreProvider(<EmailVerificationPending />),
);
await waitFor(() => {
expect(
getByTestId('emailVerificationPendingScreen_moreInfoCta'),
).toBeTruthy();
});
fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta'));
expect(navigation.navigate).toHaveBeenCalledWith(
RootScreens.SignUpNavigator,
{
screen: SignUpScreens.EmailVerificationResend,
params: {
email: '',
},
},
);
});
Error message:
● features/signup/presentation/email-verification/pending-screen › should navigate to resend screen when button is pressed
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "RootScreens_SignUpNavigator", {"params": {"email": ""}, "screen": "SignUpScreens_EmailVerificationResend"}
Number of calls: 0
64 | fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta'));
65 |
> 66 | expect(navigation.navigate).toHaveBeenCalledWith(
This error message is likely occurring when you are trying to test a mocked function using the Jest testing framework, and the test is failing because the mocked function is being called but the assertion toHaveBeenCalledWith is not recognizing it.
Here are some possible solutions:
Make sure that you are importing the correct mock function and that
it is being called in the correct place in your code.
Check that the arguments passed to the mocked function match the
arguments passed to toHaveBeenCalledWith
check if the mock implementation is correct, you should use
jest.fn() or jest.spyOn() to create a mock function
Make sure that you are using the correct syntax for the
toHaveBeenCalledWith assert.
Try to debug the test to check if the mocked function is getting
called or not.
Make sure that you are correctly importing and calling the
navigation function, and that it is being called with the correct
arguments and parameters.
Make sure that the navigation function is being called after the
button press event and not before.
Make sure that the navigation function is being called in the
correct screen and the test is not looking for it in wrong screen.
Try using .toHaveBeenCalled() instead of .toHaveBeenCalledWith()
Make sure that the test case is not getting skipped or ignored.

Jest: Testing window.location.reload

How do I write a test that makes sure that the method reloadFn does in fact reload the window? I found this resource but I am unclear on how to expect a window reload when writing a test when that window reload happens in a given function. Thanks for the help!
const reloadFn = () => {
window.location.reload(true);
}
Updated Answer (November 2021)
Package:
"jest": "^26.6.0"
"#testing-library/jest-dom": "^5.11.4"
Build: create-react-app 4
describe("test window location's reload function", () => {
const original = window.location;
const reloadFn = () => {
window.location.reload(true);
};
beforeAll(() => {
Object.defineProperty(window, 'location', {
configurable: true,
value: { reload: jest.fn() },
});
});
afterAll(() => {
Object.defineProperty(window, 'location', { configurable: true, value: original });
});
it('mocks reload function', () => {
expect(jest.isMockFunction(window.location.reload)).toBe(true);
});
it('calls reload function', () => {
reloadFn(); // as defined above..
expect(window.location.reload).toHaveBeenCalled();
});
});
Note: Updated answer because the the old answer wasn't supported with latest jest version used in CRA.
Old answer
Here’s the solution but refactored for better organization:
describe('test window location\'s reload function', () => {
const { reload } = window.location;
beforeAll(() => {
Object.defineProperty(window.location, 'reload', {
configurable: true,
});
window.location.reload = jest.fn();
});
afterAll(() => {
window.location.reload = reload;
});
it('mocks reload function', () => {
expect(jest.isMockFunction(window.location.reload)).toBe(true);
});
it('calls reload function', () => {
reloadFn(); // as defined above..
expect(window.location.reload).toHaveBeenCalled();
});
});
Thanks :)
If you use TypeScript with Jest:
Idea
Create a copy and then delete window's location property.
Now set location property with reload function mocked.
Set the original value back when test completes.
Code: TypeScript 3.x and below
const location: Location = window.location;
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
// <code to test>
// <code to test>
// <code to test>
expect(window.location.reload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
window.location = location;
Code: TypeScript 4+
TypeScript 4 has stricter checks (which is a good thing), so I am not really sure if there's a way to do this other than to suppress the error using #ts-ignore or #ts-expect-error.
WARNING: Suppressing TypeScript validations can be 🔴dangerous🔴.
const location: Location = window.location;
// WARNING:
// #ts-ignore and #ts-expect-error suppress TypeScript validations by ignoring errors.
// Suppressing TypeScript validations can be 🔴dangerous🔴.
// #ts-ignore
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
// <code to test>
// <code to test>
// <code to test>
expect(window.location.reload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
window.location = location;
You can also simplify Murtaza Hussain's solution to
describe('refreshPage', () => {
const { reload } = window.location;
beforeAll(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: { reload: jest.fn() },
});
});
afterAll(() => {
window.location.reload = reload;
});
it('reloads the window', () => {
refreshPage();
expect(window.location.reload).toHaveBeenCalled();
});
});
You could use sessionStorage to save a value for each reload.
As long as the browser does not close, the value will remain in sessionStorage.
When the page reloads the value will increment. Verify the fresh reload with this value.
Test this by pasting reloadFn() into the console.
The console will display Reload count: 1, and increment with each reload.
const reloadFn = () => {
window.location.reload(true);
}
window.onload = function() {
// get reloadCount from sessionStorage
reloadCount = sessionStorage.getItem('reloadCount');
// reloadCount will be null the first page load or a new value for each reload
if (reloadCount) {
// increment reloadCount
reloadCount = parseInt(reloadCount) + 1;
// save the new value to sessionStorage
sessionStorage.setItem('reloadCount', reloadCount);
console.log("Reload count: " + reloadCount);
} else {
// if reloadCount was null then set it to 1 and save to sessionStorage
sessionStorage.setItem('reloadCount', 1);
console.log("Page was loaded for the first time");
}
}
Inside your function testing reloadFn, you should use the mock code you linked to:
Object.defineProperty(window.location, 'reload', {
configurable: true,
}); // makes window.location.reload writable
window.location.reload = jest.fn(); // set up the mock
reloadFn(); // this should call your mock defined above
expect(window.location.reload).toHaveBeenCalled(); // assert the call
window.location.reload.mockRestore(); // restore window.location.reload to its original function
For a more improved test, you can use
expect(window.location.reload).toHaveBeenCalledWith(true);
Of note is that this is not actually verifying that the window is reloaded which is outside of the scope of a unit test. Something like browser testing or integration testing would verify that.
Instead of using workarounds with Object.defineProperty you can use native Jest's spyOn function in the following way:
test("reload test", () => {
const { getByText } = renderComponentWithReloadButton()
const reload = jest.fn()
jest
.spyOn(window, "location", "get")
.mockImplementation(() => ({ reload } as unknown as Location))
// Call an action that should trigger window.location.reload() function
act(() => {
getByText("Reload me").click()
})
// Test if `reload` function was really called
expect(reload).toBeCalled()
})
Also make sure you clear the mock after test by using for example jest.clearAllMocks() function.
If someone is looking this up in 2020, well i had the same issued.
Why is the problem happening for some and not for others?
Well it all depends on the version of chrome you are running, i wrote a test for a component that eventually called window.location.reload. Below is the section of the component code:
onConfirmChange() {
const {data, id} = this.state;
this.setState({showConfirmationModal: false}, () => {
this.update(data, id)
.then(() => window.location.reload());
});
}
The test initially failed on my build server with chrome version 71, it was passing for my local with chrome version 79.
I updated my chrome today to version 84, and the problem popped up in my local
The deleting of window.local seems to be un supported. Tried all the solution i could find in google nothing worked.
What is the solution then?
It was actually very simple, for react testing my system is using enzyme so what i did was wrapped the window.location.reload in an instance method and stubbed that in the test
JSX code:
reloadWindow() {
window.location.reload();
}
onConfirmChange() {
const {data, id} = this.state;
this.setState({showConfirmationModal: false}, () => {
this.update(data, id)
.then(() => reloadWindow());
});
}
TEST
it('check what happened', () => {
render();
const component = wrapper.instance();
sandbox.stub(component, 'reloadWindow').callsFake();
});
Updated Answer (May 2021)
I ran into some issues with a lot of the answers in the thread. I think version changes over time to the underlying libraries caused the breakage.
My Config:
"typescript": "~4.1.5"
"jest": "^26.6.3"
"jest-environment-jsdom": "^26.6.2"
Also, I should note, my solution is pretty verbose. But my use case needs to test window.location.replace() and the outcome. So I couldn't
simply mock window.location.replace. If you just need to mock one of the functions and don't care how the actual href changes, then some of the solutions in the thread will work great with less code.
Working Version
I found that completely polyfilling the window.location object solved all my problems.
window.location polyfill
Use this code and put it anywhere in your test file or setup files:
export class MockWindowLocation {
private _url: URL = new URL();
get href (): string {
return this._url.toString();
}
set href (url: string) {
this._url = new URL(url);
}
get protocol (): string {
return this._url.protocol;
}
get host (): string {
return this._url.host;
}
get hostname (): string {
return this._url.hostname;
}
get origin (): string {
return this._url.origin;
}
get port (): string {
return this._url.port;
}
get pathname (): string {
return this._url.pathname;
}
get hash (): string {
return this._url.hash;
}
get search (): string {
return this._url.search;
}
replace = jest.fn().mockImplementation((url: string) => {
this.href = url;
});
assign = jest.fn().mockImplementation((url: string) => {
this.href = url;
});
reload = jest.fn();
toString(): string {
return this._url.toString();
}
}
Test it
Then you have to delete window.location and set it to the new polyfill:
it('should be able to test window.location', () => {
delete window.location;
Object.defineProperty(window, 'location', {
value: new MockWindowLocation()
});
window.location.href = 'https://example.com/app/#/route/1';
window.location.reload();
expect(window.location.reload).toHaveBeenCalled();
expect(window.location.href).toBe('https://example.com/app/#/route/1');
expect(window.location.pathname).toBe('/app/');
expect(window.location.hash).toBe('#/route/1');
});
This works wonders for me. Hope it helps someone else out.
Other answers are simplier
To re-iterate, there are other answers in this thread that work just fine. I found:
Object.defineProperty(window, 'location', {
writable: true,
value: { reload: jest.fn() },
});
And:
const location: Location = window.location;
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
both to be helpful. But like I said, I needed to spy on replace() and still have the standard functionality of window.location.
Hope this helps someone. Cheers!

Jasmine Data Provider is not working (jasmine_data_provider_1.using is not a function)

I am trying to achieve Data Driven testing in my project by using jasmine data providers.
I have a data.ts file like below
export const hardshipTestData = {
scenarios: {
scenario1: {
isHome: 'Yes'
},
scenario2: {
isHome: 'No'
}
}
};
I am using above data in spec file
import { using } from 'jasmine-data-provider';
import { hardshipTestData } from '../../data/testdata';
using(hardshipTestData.scenarios, function (data, description) {
it('testing data providers', () => {
console.log(data.isHome);
});
});
My issue here is when I am trying to write data. intelligence is not even giving the option isHome. When I enforce it and run the test I am getting the following error
TestSuite encountered a declaration exception
configuration-parser.js:48
- TypeError: jasmine_data_provider_1.using is not a function
any help is appreciated
You need to change import type. Try to replace:
import { using } from 'jasmine-data-provider';
with:
const using = require('jasmine-data-provider');
Also, keep in mind that firstly should be describe block:
describe('example test', () => {
using(hardshipTestData.scenarios, (data) => {
it('should calc with operator -', () => {
console.log(data.isHome);
});
});
});
Adding to Oleksii answer, his answer is for typescript.
but If you want to use in plain javascript use below:
Add below in your code:
var using = require('jasmine-data-provider');
Example:
var jasminedatasetobj = require("./jasmineDataDrivenData");
var using = require('jasmine-data-provider');
using(jasminedatasetobj.datadrive, function (data, description) {
it('Open NonAngular js website Alerts', async() => {
await browser.get("https://qaclickacademy.github.io/protocommerce/");
element(by.name("name")).sendKeys(data.name);
});
});
You might need to give full path of Jasmine data provider for plain javascripts to avoid module not found error.
var jsondataobj = require('../../../../config/Jsoninput.json');//define the data source location
var using = require('C:/Users/sam/AppData/Roaming/npm/node_modules/jasmine-data-provider');
describe("Test Jasmine Data provider",function(){
you need to declare the variable for "jasmine-data-provider" , because import can use to import the properties/classes.
instead of using variable you can give any name to the varible (I tried use "post" instead of "using" and it is still working as expected)
your code should be like
import { hardshipTestData } from "../Test";
const using = require("jasmine-data-provider");
describe("Login TestCases", () => {
using(hardshipTestData.scenarios, (alldata: any, alldesc: any) => {
it("login with different credentials", async () => {
console.log(data.isHome);
})
})
})
this will resolve you problem.

testing chrome.storage.local.set with jest

I'm a very beginner with testing and started using jest. This function I have saves data in chrome.storage.local and I want to test if chrome.storage.local called 3 times. Here is the function:
saveData: function(){
chrome.storage.local.set({'blackList': bgModule.blackList}, function() {});
chrome.storage.local.set({'pastDays': bgModule.pastDays}, function() {});
chrome.storage.local.set({'websiteList': bgModule.websiteList}, function() {});
}
and here is my test:
const bgModule = require("../../app/Background/background.js");
const chrome = {
storage: {
local: {
set: function() {},
get: function() {}
}
}
};
let blackListStub = bgModule.blackList;
let pastDaysStub = [
{day: "day1"},
{day2: "day2"},
{day3: "day3"}];
let websiteListStub = [
{websiteName: "facebook.com"},
{websiteName: "stackoverflow.com"},
{websiteName: "github.com"}];
it ("should save data in local storage", () => {
spyOn(bgModule, 'saveData');
bgModule.saveData();
const spy = spyOn(chrome.storage.local, 'set');
spy({'blackList' : blackListStub});
spy({'pastDays' : pastDaysStub});
spy({'websiteList' : websiteListStub});
expect(spy).toHaveBeenCalledWith({'blackList' : blackListStub});
expect(spy).toHaveBeenCalledWith({'pastDays' : pastDaysStub});
expect(spy).toHaveBeenCalledWith({'websiteList' : websiteListStub});
expect(bgModule.saveData).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(3);
});
Test passes but it's incorrect. I want to make it work so when I change saveData it will break the test. The test I have has everything hardcoded into it and doesn't depend on saveData at all. I looked for example and tutorial materials and couldn't find anything for this case. Any help or direction I should take would be very helpful.
After help and correct answer with use of global.chrome refactored test looks like this:
it ("should save data in local storage", () => {
bgModule.saveData();
expect(chrome.storage.local.set).toHaveBeenCalledTimes(3);
});
The easiest thing would be to use dependency injection, so instead of calling chrome.storage.local directly, passed it as an argument to your function that then can be easily replaced by a mock in your test.
The other way would be to add it as a global variable into your test environment like this:
const get = jest.fn()
const set = jest.fn()
global.chrome = {
storage: {
local: {
set,
get
}
}

Testing a custom method for $state.go

I tried to test this code:
redireccion() {
this.$state.go('modifyLine', {lineId: this.look()._id});
}
look() {
return Entries.findOne({name: this.entry.name});
}
the code above method is ok (look), but for 'redireccion' I tried something like this and i got an error.
this is the code:
describe('redireccion()', () => {
beforeEach( inject(($state) => {
spyOn($state, 'go');
spyOn(controller, 'look');
spyOn(Entries, 'findOne');
}));
it('should be a ... bla bla', () => {
controller.redireccion();
expect($state.go).toHaveBeenCalledWith('modifyLine', {lineId: });
});
});
This is an excerpt, because really I do not know how testing this.
I will try to give you an insight. You should try to make your tests isolated... That means that if you're testing your redirection, you can mock the look method since it's not relevant (for this specific test).
describe('testing redirection()', () => {
beforeEach( inject(($state) => {
//here I'm saying that I'm spying every call to $state.go
spyOn($state, 'go');
//And here I'm that I'm not only spying every call to
//controller.look() but I'm also replacing the original
//implementation with my fake one. Every call will basically
//return an object with id equals 10
spyOn(controller, 'look').and.callFake(() => {
var mockedLine = {
_id: 10
};
return mockedLine;
});
}));
it('should call state.go', () => {
controller.redireccion();
//if you just want to check if the method was called, do the following:
expect($state.go).toHaveBeenCalled();
//if you need to also check the arguments, try:
var args = $state.go.mostRecentCall.args;
expect(args[0]).toBe('modifyLine');
expect(args[1].lineId).toBe(10);
});
});

Categories

Resources