How can I mock the JavaScript window object using Jest?
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?
Solution 1:
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:
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:
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:
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();
});