Currently, I am implementing unit test for my project and there is a file that contained window.location.href
.
I want to mock this to test and here is my sample code:
it("method A should work correctly", () => {
const url = "http://dummy.com";
Object.defineProperty(window.location, "href", {
value: url,
writable: true
});
const data = {
id: "123",
name: null
};
window.location.href = url;
wrapper.vm.methodA(data);
expect(window.location.href).toEqual(url);
});
But I get this error:
TypeError: Cannot redefine property: href
at Function.defineProperty (<anonymous>)
I had tried some solutions but not resolve it. I need some hints to help me get out of this trouble. Plz help.
window.location.assign(url)
functionally does the same thing so you could mock that instead using jest.spyOn(window.location, 'assign').mockImplementation(() => {});
You can try:
global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, 'location', {
value: {
href: url
}
});
expect(window.location.href).toEqual(url);
Have a look at the Jest Issue for that problem:
Jest Issue
2020 Update
Basic
The URL object has a lot of the same functionality as the Location object. In other words, it includes properties such as pathname
, search
, hostname
, etc. So for most cases, you can do the following:
delete window.location
window.location = new URL('https://www.example.com')
Advanced
You can also mock Location methods that you might need, which don't exist on the URL interface:
const location = new URL('https://www.example.com')
location.assign = jest.fn()
location.replace = jest.fn()
location.reload = jest.fn()
delete window.location
window.location = location
delete window.location
will trigger an error The operand of a 'delete' operator must be optional
Reflect.deleteProperty(global.window, 'location')
deals with that without ts error
delete (window as any).location;
I have resolved this issue by adding writable: true
and move it to beforeEach
Here is my sample code:
global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
value: {
href: url
},
writable: true
});
window.location.hostname
in Jest tests. I needed writable: true
in order to change the hostname
more than once.
Solution for 2019 from GitHub:
delete global.window.location; global.window = Object.create(window); global.window.location = { port: '123', protocol: 'http:', hostname: 'localhost', };
location.host
property
The best is probably to create a new URL
instance, so that it parses your string like location.href
does, and so it updates all the properties of location
like .hash
, .search
, .protocol
etc.
it("method A should work correctly", () => {
const url = "http://dummy.com/";
Object.defineProperty(window, "location", {
value: new URL(url)
} );
window.location.href = url;
expect(window.location.href).toEqual(url);
window.location.href += "#bar"
expect(window.location.hash).toEqual("#bar");
});
https://repl.it/repls/VoluminousHauntingFunctions
Many of the examples provided doesn't mock the properties of the original Location object.
What I do is just replace Location object (window.location) by URL, because URL contains the same properties as Location object like "href", "search", "hash", "host".
Setters and Getters also work exactly like the Location object.
Example:
const realLocation = window.location;
describe('My test', () => {
afterEach(() => {
window.location = realLocation;
});
test('My test func', () => {
// @ts-ignore
delete window.location;
// @ts-ignore
window.location = new URL('http://google.com');
console.log(window.location.href);
// ...
});
});
const realLocation = Object.assign({}, window.location);
since I feel just assigning it directly would be passing a reference which is later overwritten. Thoughts?
Working example with @testing-library/react
in 2020 for window.location.assign:
afterEach(cleanup)
beforeEach(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: { assign: jest.fn() }
})
})
writable: true
was necessary for my unit tests to work otherwise the subsequent tests wouldn't be able to overwrite it to something else. Thanks
Extending @jabacchetta's solution to avoid this setting bleeding into other tests:
describe("Example", () => {
let location;
beforeEach(() => {
const url = "https://example.com";
location = window.location;
const mockLocation = new URL(url);
mockLocation.replace = jest.fn();
delete window.location;
window.location = mockLocation;
});
afterEach(() => {
window.location = location;
});
});
You can try a helper:
const setURL = url => global.jsdom.reconfigure({url});
describe('Test current location', () => {
test('with GET parameter', () => {
setURL('https://test.com?foo=bar');
// ...your test here
});
});
This is valid for Jest + TypeScript + Next.js (in case you use useRoute().push
const oldWindowLocation = window.location;
beforeAll(() => {
delete window.location;
window.location = { ...oldWindowLocation, assign: jest.fn() };
});
afterAll(() => {
window.location = oldWindowLocation;
});
JSDOM Version
Another method, using JSDOM, which will provide window.location.href
and all of the other properties of window.location
, (e.g. window.location.search
to get query string parameters).
import { JSDOM } from 'jsdom';
...
const { window } = new JSDOM('', {
url: 'https://localhost/?testParam=true'
});
delete global.window;
global.window = Object.create(window);
How to reassign window.location in your code base; the simplest working setup we found for our Jest tests:
const realLocation = window.location;
beforeEach(() => {
delete window.location;
});
afterEach(() => {
window.location = realLocation;
});
you can try jest-location-mock.
npm install --save-dev jest-location-mock
update jest
configs at jest.config.js
file or jest
prop inside package.json
:
setupFilesAfterEnv: [ "./config/jest-setup.js" ]
create jest-setup.js
import "jest-location-mock";
usage:
it("should call assign with a relative url", () => {
window.location.assign("/relative-url");
expect(window.location).not.toBeAt("/");
expect(window.location).toBeAt("/relative-url");
});
Can rewrite window.location by delete this global in every test.
delete global.window.location;
const href = 'http://localhost:3000';
global.window.location = { href };
Based on examples above and in other threads, here is a concrete example using jest
that might help someone:
describe('Location tests', () => {
const originalLocation = window.location;
const mockWindowLocation = (newLocation) => {
delete window.location;
window.location = newLocation;
};
const setLocation = (path) =>
mockWindowLocation(
new URL(`https://example.com${path}`)
);
afterEach(() => {
// Restore window.location to not destroy other tests
mockWindowLocation(originalLocation);
});
it('should mock window.location successfully', () => {
setLocation('/private-path');
expect(window.location.href).toEqual(
`https://example.com/private-path`
);
});
});
Probably irrelevant. But for those seeking a solution for window.open('url', attribute) I applied this, with help of some comments above:
window = Object.create(window);
const url = 'https://www.9gag.com';
Object.defineProperty(window, 'open', { value: url });
expect(window.open).toEqual(url);
Here's a simple one you can use in a beforeEach
or ala carte per test.
It utilizes the Javascript window.history
and its pushState
method to manipulate the URL.
window.history.pushState({}, 'Enter Page Title Here', '/test-page.html?query=value');
Success story sharing
global
didn't work, I removedglobal.
and I also needed to addwritable: true
otherwise once set the other tests can't change it.