'Intercept navigation change with jest.js (or how to override and restore location.href)
Application code is calling location.href = "some-url"
.
I want to write a test that verify the navigation redirect has happened.
Using jest on jsdom, I tried to do it with overriding location.href setter using jest mock function and it is working.
But now I can't seems to restore the location.href property at the test cleanup, and it failing the rest of the tests which relay on 'location.href'.
it('test navigation happened', () => {
const originalLocationHref = Object.getOwnPropertyDescriptor(window.location, 'href'); // returns undefined
spyFn = jest.fn();
Object.defineProperty(window.location, 'href', {
set: spyFn,
enumerable: true,
configurable: true
});
someAppCodeThatShouldRedirectToSomeUrl();
expect(spyFn).toBeCalledWith('http://some-url'); // this is working
// Cleanup code, not working, because originalLocationHref is undefined
Object.defineProperty(window.location, 'href', originalLocationHref);
});
What am I missing? Why Object.getOwnPropertyDescriptor(window.location, 'href');
is undefined
?
Is there a better way to intercept navigation events in order to test it?
Thanks
Solution 1:[1]
Use location.assign() method instead instead of assigning new location string to location.href. Then you can mock and test it with no problems:
it('test navigation happened', () => {
window.location.assign = jest.fn();
// here you call location.assign('http://some-url');
redirectToSomeUrl();
expect(window.location.assign).toBeCalledWith('http://some-url');
// location.href hasn't changed because location.assign was mocked
});
Solution 2:[2]
Newer jest/jsdom versions do not allow to set window.location.assign
anymore. It can be fixed like this:
delete window.location;
window.location = { assign: jest.fn() };
Note that this removes all other objects from window.location, you might need to mock more of its objects depending on your test and application code.
Source: https://remarkablemark.org/blog/2018/11/17/mock-window-location/
Solution 3:[3]
As quotesBro already explained in his answer, you should rather use location.assign().
But since Jest v25 (which uses a newer version of JSDOM) you will get the following error:
TypeError: Cannot assign to read only property 'assign' of object '[object Location]'
This is not a Jest/JSDOM bug by the way. This is normal browser behaviour and JSDOM tries to act like a real browser.
A workaround is to remove the location object, create your own one and after running your tests you should reset it to the original location object:
describe('My awesome unit test', () => {
// we need to save the original object for later to not affect tests from other files
const realLocation = window.location
beforeAll(() => {
delete window.location
window.location = { assign: jest.fn() }
// or even like this if you are also using other location properties (or if Typescript complains):
// window.location = { ...realLocation, assign: jest.fn() }
})
afterAll(() => {
window.location = realLocation
})
it('should call location.assign', () => {
// ...your test code
expect(window.location.assign).toHaveBeenCalled()
// or even better:
// expect(window.location.assign).toHaveBeenCalledWith('/my_link')
})
})
Solution 4:[4]
Another pretty simple solution can be to mock the location window.location object at the beginning of the test file
const assignSpy = jest.fn();
Object.defineProperty(window, 'location', {
value: { assign: assignSpy }
});
describe('My awesome unit test', () => {
it('should call location.assign', () => {
// ...your test code
expect(window.location.assign).toHaveBeenCalled()
// or even better:
// expect(window.location.assign).toHaveBeenCalledWith('/my_link')
})
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | fabb |
Solution 3 | |
Solution 4 | samuele piazzesi |