如何模拟JavaScript '窗口'对象使用Jest?

我需要测试一个在浏览器中打开一个新选项卡的功能

openStatementsReport(contactIds) {
window.open(`a_url_${contactIds}`);
}

我想模拟窗口open函数,这样我就可以验证传递给open函数的正确URL。

使用Jest,我不知道如何mock window。我尝试用模拟函数设置window.open,但这种方式不起作用。下面是测试用例:

it('the 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]

我应该对测试用例做什么?

262404 次浏览

使用global代替window:

it('the correct URL is called', () => {
global.open = jest.fn();
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});

你也可以试试:

const open = jest.fn()
Object.defineProperty(window, 'open', open);

你可以试试这个:

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();
});

我们也可以在setupTests中使用global定义它:

// File 'setupTests.js'
global.open = jest.fn()

并在实际测试中使用global调用它:

// File 'yourtest.test.js'
it('the correct URL is called', () => {
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});

在Jest配置中,添加setupFilesAfterEnv: ["./setupTests.js"],创建该文件,并添加要在测试之前运行的代码:

// setupTests.js
window.crypto = {
.....
};

参考:< a href = " https://jestjs。io/docs/ zh /configuration# setupFilesAfterEnv -array" rel="nofollow noreferrer">setupFilesAfterEnv [array] . conf . conf [array] . conf . conf . conf

下面的方法对我很有效。这种方法允许我测试一些在浏览器和Node.js中都可以工作的代码,因为它允许我将window设置为undefined

这是笑话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();
});

在Jest中有几种方法可以模拟全局变量:

  1. 使用mockImplementation方法(最像玩笑的方式),但它只对那些有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();
    });
    
  2. 直接将值赋给全局属性。这是最直接的,但它可能会触发一些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;
    });
    
  3. 不要直接使用全局变量(需要一些重构)

    与其直接使用全局值,不如从另一个文件导入它,因此使用Jest时,mock将变得微不足道。

文件. / . 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}`);
}
}

我找到了一个简单的方法:删除和替换

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
});
});

在组件中,我需要访问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;
});

我直接将jest.fn()赋值给window.open

window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','_blank')

你可以测试它:

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')
})
})

试一试:

let windowOpenSpy: jest.SpyInstance;
beforeEach(() => {
windowOpenSpy = jest.spyOn(window, 'open');
});


it('should open window with dashboard url', () => {
expect(windowOpenSpy).toBeCalledWith('your URL', '_blank');
});
const windowSpy = jest.spyOn(iFrame, "contentWindow", "get");
windowSpy.mockImplementation(() => ({
location: {
origin: "https://test.com",
href: "href",
hash: "hash"
}
}));

我有一个实用函数,它允许我模拟窗口上的任何方法,如下所示:

  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()


我试过一个类似的测试,对我很有效……

我的代码:

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
})

Jest中的window对象是自我嘲笑

其他答案中没有提到的一件事是OP的评论:

使用Jest,我不知道如何模拟window

window对象是已经嘲笑,可以开箱引用。

的文档:

Jest附带jsdom,它模拟DOM环境,就像您在浏览器中一样。这意味着我们调用的每个DOM API都可以用在浏览器中观察到的相同方式观察到!

这里有一个非常简单的例子来说明这个概念:

describe('i am a window', () => {
it('has a window object', () => {
expect(window).toBeTruthy(); // test will pass
});
});