如何使用 Jest 模拟 JavaScript 窗口对象?
Posted
技术标签:
【中文标题】如何使用 Jest 模拟 JavaScript 窗口对象?【英文标题】:How can I mock the JavaScript window object using Jest? 【发布时间】:2017-06-12 15:43:17 【问题描述】:我需要测试一个在浏览器中打开新标签的函数
openStatementsReport(contactIds)
window.open(`a_url_$contactIds`);
我想模拟窗口的 open
函数,以便验证正确的 URL 是否传递给 open
函数。
使用 Jest,我不知道如何模拟 window
。我尝试使用模拟函数设置window.open
,但这种方式不起作用。下面是测试用例
it('correct url is called', () =>
window.open = jest.fn();
statementService.openStatementsReport(111);
expect(window.open).toBeCalled();
);
但它给了我错误
expect(jest.fn())[.not].toBeCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function anonymous]
我应该对测试用例做什么?
【问题讨论】:
有没有计划将***.com/a/56999581/9888500 设置为接受的答案? 【参考方案1】:以下方法对我有用。这种方法允许我测试一些在浏览器和 Node.js 中都可以工作的代码,因为它允许我将 window
设置为 undefined
。
这是 Jest 24.8(我相信):
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();
);
【讨论】:
这比Object.defineProperty
好得多,因为这样在模拟时不会影响其他测试。
这应该是公认的答案,因为它模拟/间谍而不是更改实际的全局属性
我只使用了x = jest.spyOn(window, 'open')
和x.mockImplementation(() => )
,仅供参考,但我只需要模拟window.open。【参考方案2】:
用global
代替window
it('correct url is called', () =>
global.open = jest.fn();
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
);
你也可以试试
const open = jest.fn()
Object.defineProperty(window, 'open', open);
【讨论】:
试过这个但对我不起作用。我的情况很奇怪,模拟在本地工作,但不适用于 Travis 中的 PR 合并......有什么想法吗? @AlexJM 你有同样的问题吗?介意分享一下你是如何模拟窗口对象的? 我只是在我的测试中定义了 window.property @Andreas 有没有办法将窗口模拟为 undefined ***.com/questions/59173156/… 谢谢!几个小时后,我只需要将window
更改为global
【参考方案3】:
在 Jest 中有几种方法可以模拟全局变量:
使用mockImplementation
方法(最类似Jest 的方法),但它仅适用于那些具有jsdom
提供的一些默认实现的变量。 window.open
就是其中之一:
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();
);
将值直接分配给全局属性。这是最直接的,但它可能会触发某些window
变量的错误消息,例如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;
);
不要直接使用全局变量(需要一些重构)
与其直接使用全局值,不如从另一个文件中导入它可能更简洁,因此使用 Jest 进行模拟将变得微不足道。
文件./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();
);
文件 ./fileWithGlobalValueExported.js
export const windowOpen = window.open;
文件 ./testedFile.js
import windowOpen from './fileWithGlobalValueExported.js';
export const statementService =
openStatementsReport(contactIds)
windowOpen(`a_url_$contactIds`);
【讨论】:
【参考方案4】:我们也可以在setupTests
中使用global
来定义它
// setupTests.js
global.open = jest.fn()
并在实际测试中使用global
调用它:
// yourtest.test.js
it('correct url is called', () =>
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
);
【讨论】:
【参考方案5】:在我的组件中,我需要访问window.location.search
。这是我在 Jest 测试中所做的:
Object.defineProperty(global, "window",
value:
location:
search: "test"
);
如果不同测试中的窗口属性必须不同,我们可以将窗口模拟放入一个函数中,并使其可写以覆盖不同的测试:
function mockWindow(search, pathname)
Object.defineProperty(global, "window",
value:
location:
search,
pathname
,
writable: true
);
并在每次测试后重置:
afterEach(() =>
delete global.window.location;
);
【讨论】:
【参考方案6】:我直接将jest.fn()
分配给window.open
。
window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','_blank')
【讨论】:
谢谢这是最好的方法! 干净简洁!太棒了!【参考方案7】:我找到了一个简单的方法:删除和替换
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
);
);
【讨论】:
【参考方案8】:你可以试试这个:
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();
);
【讨论】:
【参考方案9】:如果类似window.location.href can't be changed in tests. #890的窗口位置问题,你可以试试(调整):
delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
【讨论】:
【参考方案10】:在您的 Jest 配置中,添加 setupFilesAfterEnv: ["./setupTests.js"],创建该文件,并在测试之前添加您要运行的代码:
// setupTests.js
window.crypto =
.....
;
参考:setupFilesAfterEnv [array]
【讨论】:
【参考方案11】:可以测试一下:
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')
)
)
【讨论】:
【参考方案12】:我有一个实用函数,它允许我像这样模拟窗口上的任何方法:
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());
因此,如果我需要在窗口上模拟 open
方法(或其他任何东西),我可以这样做:
const cleanupMocks = givenMockWindowMethods( open: jest.fn() );
// expect(...).toBe(...)
//at the end of the test, clean it up
cleanupMocks()
【讨论】:
【参考方案13】:简单试试
let windowOpenSpy: jest.SpyInstance;
beforeEach(() =>
windowOpenSpy = jest.spyOn(window, 'open');
);
it('should open window with dashboard url', () =>
expect(windowOpenSpy).toBeCalledWith('your url', '_blank');
);
【讨论】:
【参考方案14】:const windowSpy = jest.spyOn(iFrame, "contentWindow", "get");
windowSpy.mockImplementation(() => (
location:
origin: "https://test.com",
href: "href",
hash: "hash"
));
【讨论】:
【参考方案15】:我尝试了一个类似的测试并和我一起工作......
我的代码:
export const Blah = () =>
const BLAH = 'https://www.google.com/'
const handleBlah = () =>
window.open(BLAH, '_blank')
return (
<button onClick=handleBlah> BLAHBLAH </button>
)
我使用 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
)
【讨论】:
【参考方案16】:Jest 中的 window
对象是自嘲的
其他答案中未解决的事情之一是 OP 的评论:
使用 Jest,我不知道如何模拟
window
。
答案出人意料地不言自明:window
对象已经被模拟,可以毫不费力地引用它。这是由于jest
的现代版本默认使用jsdom
环境。
来自docs:
Jest 附带 jsdom,它模拟 DOM 环境,就像您在浏览器中一样。这意味着我们调用的每个 DOM API 都可以像在浏览器中一样被观察!
这里有一个非常简单的例子来说明这个概念:
describe('i am a window', () =>
it('has a window object', () =>
expect(window).toBeTruthy(); // test will pass yay
);
);
诚然,这并没有涉及到 window.open
,但由于其他人可能会在此页面上寻找有关如何模拟 window
本身的信息,我希望这里的答案会有所帮助。
【讨论】:
以上是关于如何使用 Jest 模拟 JavaScript 窗口对象?的主要内容,如果未能解决你的问题,请参考以下文章