How can I mock the JavaScript window object using Jest?
JavascriptMockingJestjsJavascript Problem Overview
I need to test a function which opens a new tab in the browser
openStatementsReport(contactIds) {
window.open(`a_url_${contactIds}`);
}
I would like to mock the window's open
function so I can verify the correct URL is passed in to the open
function.
Using Jest, I don't know how to mock the window
. I tried to set window.open
with a mock function but this way doesn't work. Below is the test case
it('correct url is called', () => {
window.open = jest.fn();
statementService.openStatementsReport(111);
expect(window.open).toBeCalled();
});
But it gives me the error
expect(jest.fn())[.not].toBeCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function anonymous]
What should I do to the test case?
Javascript Solutions
Solution 1 - Javascript
The following method worked for me. This approach allowed me to test some code that should work both in the browser and in Node.js, as it allowed me to set window
to undefined
.
This was with Jest 24.8 (I believe):
let windowSpy;
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
});
afterEach(() => {
windowSpy.mockRestore();
});
it('should return https://example.com', () => {
windowSpy.mockImplementation(() => ({
location: {
origin: "https://example.com"
}
}));
expect(window.location.origin).toEqual("https://example.com");
});
it('should be undefined.', () => {
windowSpy.mockImplementation(() => undefined);
expect(window).toBeUndefined();
});
Solution 2 - Javascript
Instead of window
use global
it('correct url is called', () => {
global.open = jest.fn();
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});
you could also try
const open = jest.fn()
Object.defineProperty(window, 'open', open);
Solution 3 - Javascript
There are a couple of ways to mock globals in Jest:
-
Use the
mockImplementation
approach (the most Jest-like way), but it will work only for those variables which has some default implementation provided byjsdom
.window.open
is one of them:test('it works', () => { // Setup const mockedOpen = jest.fn(); // Without making a copy, you will have a circular dependency problem const originalWindow = { ...window }; const windowSpy = jest.spyOn(global, "window", "get"); windowSpy.mockImplementation(() => ({ ...originalWindow, // In case you need other window properties to be in place open: mockedOpen })); // Tests statementService.openStatementsReport(111) expect(mockedOpen).toBeCalled(); // Cleanup windowSpy.mockRestore(); });
-
Assign the value directly to the global property. It is the most straightforward, but it may trigger error messages for some
window
variables, e.g.window.href
.test('it works', () => { // Setup const mockedOpen = jest.fn(); const originalOpen = window.open; window.open = mockedOpen; // Tests statementService.openStatementsReport(111) expect(mockedOpen).toBeCalled(); // Cleanup window.open = originalOpen; });
-
Don't use globals directly (requires a bit of refactoring)
Instead of using the global value directly, it might be cleaner to import it from another file, so mocking will became trivial with Jest.
File ./test.js
jest.mock('./fileWithGlobalValueExported.js');
import { windowOpen } from './fileWithGlobalValueExported.js';
import { statementService } from './testedFile.js';
// Tests
test('it works', () => {
statementService.openStatementsReport(111)
expect(windowOpen).toBeCalled();
});
File ./fileWithGlobalValueExported.js
export const windowOpen = window.open;
File ./testedFile.js
import { windowOpen } from './fileWithGlobalValueExported.js';
export const statementService = {
openStatementsReport(contactIds) {
windowOpen(`a_url_${contactIds}`);
}
}
Solution 4 - Javascript
We can also define it using global
in setupTests
// setupTests.js
global.open = jest.fn()
And call it using global
in the actual test:
// yourtest.test.js
it('correct url is called', () => {
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});
Solution 5 - Javascript
I'm directly assigning jest.fn()
to window.open
.
window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','_blank')
Solution 6 - Javascript
In my component I need access to window.location.search
. This is what I did in the Jest test:
Object.defineProperty(global, "window", {
value: {
location: {
search: "test"
}
}
});
In case window properties must be different in different tests, we can put window mocking into a function, and make it writable in order to override for different tests:
function mockWindow(search, pathname) {
Object.defineProperty(global, "window", {
value: {
location: {
search,
pathname
}
},
writable: true
});
}
And reset after each test:
afterEach(() => {
delete global.window.location;
});
Solution 7 - Javascript
I found an easy way to do it: delete and replace
describe('Test case', () => {
const { open } = window;
beforeAll(() => {
// Delete the existing
delete window.open;
// Replace with the custom value
window.open = jest.fn();
// Works for `location` too, eg:
// window.location = { origin: 'http://localhost:3100' };
});
afterAll(() => {
// Restore original
window.open = open;
});
it('correct url is called', () => {
statementService.openStatementsReport(111);
expect(window.open).toBeCalled(); // Happy happy, joy joy
});
});
Solution 8 - Javascript
You can try this:
import * as _Window from "jsdom/lib/jsdom/browser/Window";
window.open = jest.fn().mockImplementationOnce(() => {
return new _Window({ parsingMode: "html" });
});
it("correct url is called", () => {
statementService.openStatementsReport(111);
expect(window.open).toHaveBeenCalled();
});
Solution 9 - Javascript
If it's similar to the window location problem at window.location.href can't be changed in tests. #890, you could try (adjusted):
delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
Solution 10 - Javascript
In your Jest configuration, add setupFilesAfterEnv: ["./setupTests.js"], create that file, and add the code you want to run before the tests:
// setupTests.js
window.crypto = {
.....
};
Reference: setupFilesAfterEnv [array]
Solution 11 - Javascript
can test it:
describe('TableItem Components', () => {
let open_url = ""
const { open } = window;
beforeAll(() => {
delete window.open;
window.open = (url) => { open_url = url };
});
afterAll(() => {
window.open = open;
});
test('string type', async () => {
wrapper.vm.openNewTab('http://example.com')
expect(open_url).toBe('http://example.com')
})
})
Solution 12 - Javascript
I have a utility function which allows me to mock any method on the window like so:
function givenMockWindowMethods(methods: Partial<{ [key in keyof Window]: jest.Mock<any, any> }>): () => void {
const mocks = Object.values(methods);
Object.entries(methods).forEach(([key, value]) => {
Object.defineProperty(window, key, { value });
});
return (): void => mocks.forEach((mock) => mock?.mockClear());
}
So if I need to mock the open
method (or anything really) on the window, I can do:
const cleanupMocks = givenMockWindowMethods({ open: jest.fn() });
// expect(...).toBe(...)
//at the end of the test, clean it up
cleanupMocks()
Solution 13 - Javascript
window
object in Jest is self-mocking
The One of the things unaddressed in other answers is a comment by OP:
> Using Jest, I don't know how to mock the window
.
The window
object is already mocked and can be referenced out of the box.
From the docs:
> Jest ships with jsdom which simulates a DOM environment as if you were in the browser. This means that every DOM API that we call can be observed in the same way it would be observed in a browser!
Here is a very simple example to illustrate the concept:
describe('i am a window', () => {
it('has a window object', () => {
expect(window).toBeTruthy(); // test will pass
});
});
Admittedly this doesn't get into window.open
but since others might arrive on this page for info on how to mock window
itself (as I did), I'm sharing this answer as a resource.
Solution 14 - Javascript
try simply
let windowOpenSpy: jest.SpyInstance;
beforeEach(() => {
windowOpenSpy = jest.spyOn(window, 'open');
});
it('should open window with dashboard url', () => {
expect(windowOpenSpy).toBeCalledWith('your url', '_blank');
});
Solution 15 - Javascript
const windowSpy = jest.spyOn(iFrame, "contentWindow", "get");
windowSpy.mockImplementation(() => ({
location: {
origin: "https://test.com",
href: "href",
hash: "hash"
}
}));
Solution 16 - Javascript
I try a similar test and worked with me...
my code:
export const Blah = () => {
const BLAH = 'https://www.google.com/'
const handleBlah = () => {
window.open(BLAH, '_blank')
}
return (
<button onClick={handleBlah}> BLAHBLAH </button>
)
}
My test using Jest:
it('should be able to render "BLAHBLAH " button ', () => {
window.open = jest.fn();
const BLAH = 'https://www.google.com/'
const { getByText } = render(<Blah/>) // get text by my page Blah
const buttonGoToBlah = getByText('BLAHBLAH') // get button by text
fireEvent.click(buttonGoToBlah) // simulate the click event
expect(window.open).toHaveBeenCalledTimes(1) \\ expect the window.open have to been called at least once.
expect(window.open).toHaveBeenCalledWith(BLAH, '_blank'); // and the page should be the same called in my BLAH page
})