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

dan*_*nny 60 javascript mocking jestjs

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

  openStatementsReport(contactIds) {
    window.open(`a_url_${contactIds}`);
  }
Run Code Online (Sandbox Code Playgroud)

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

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

it('correct url is called', () => {
  window.open = jest.fn();
  statementService.openStatementsReport(111);
  expect(window.open).toBeCalled();
});
Run Code Online (Sandbox Code Playgroud)

但它给了我错误

expect(jest.fn())[.not].toBeCalled()

    jest.fn() value must be a mock function or spy.
    Received:
      function: [Function anonymous]
Run Code Online (Sandbox Code Playgroud)

我应该怎么做测试用例?任何建议或提示表示赞赏

And*_*rle 54

而不是window使用global

it('correct url is called', () => {
  global.open = jest.fn();
  statementService.openStatementsReport(111);
  expect(global.open).toBeCalled();
});
Run Code Online (Sandbox Code Playgroud)

你也可以试试

const open = jest.fn()
Object.defineProperty(window, 'open', open);
Run Code Online (Sandbox Code Playgroud)

  • 试过这个但不适合我.我的情况很奇怪,在当地嘲弄工作但不是Travis的公关合并......任何想法? (3认同)

jma*_*eli 17

在 Jest 中有两种模拟全局变量的方法:

  1. 使用该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();
    });
    
    Run Code Online (Sandbox Code Playgroud)
  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;
    });
    
    Run Code Online (Sandbox Code Playgroud)
  3. 不要直接使用全局变量(需要一些重构)

    不是直接使用全局值,而是从另一个文件导入它可能更干净,因此使用 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();
});
Run Code Online (Sandbox Code Playgroud)

文件./fileWithGlobalValueExported.js

export const windowOpen = window.open;
Run Code Online (Sandbox Code Playgroud)

文件./testedFile.js

import { windowOpen } from './fileWithGlobalValueExported.js';
export const statementService = {
  openStatementsReport(contactIds) {
    windowOpen(`a_url_${contactIds}`);
  }
}
Run Code Online (Sandbox Code Playgroud)


Alo*_*nad 11

在我的组件中,我需要访问window.location.search. 这是我在 Jest 测试中所做的:

Object.defineProperty(global, "window", {
  value: {
    location: {
      search: "test"
    }
  }
});
Run Code Online (Sandbox Code Playgroud)

如果窗口属性在不同的测试中必须不同,我们可以将窗口模拟放入一个函数中,并使其可写,以便覆盖不同的测试:

function mockWindow(search, pathname) {
  Object.defineProperty(global, "window", {
    value: {
      location: {
        search,
        pathname
      }
    },
    writable: true
  });
}
Run Code Online (Sandbox Code Playgroud)

并在每次测试后重置:

afterEach(() => {
  delete global.window.location;
});
Run Code Online (Sandbox Code Playgroud)


tvs*_*ent 8

以下是对我有用的方法。这种做法让我来测试一些代码,应在浏览器和节点都工作,因为它让我设置windowundefined

这就是Jest 24.8(我相信):

let windowSpy;

beforeEach(() => {
  windowSpy = jest.spyOn(global, '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();
});
Run Code Online (Sandbox Code Playgroud)

  • 这比“Object.defineProperty”好得多,因为这允许在模拟时不影响其他测试。 (3认同)
  • 这应该是公认的答案,因为它模拟/监视而不是更改实际的全局属性 (3认同)
  • 不幸的是,与打字稿不兼容。 (3认同)
  • 我只是使用了 `x = jest.spyOn(window, 'open')` 和 `x.mockImplementation(() => {})`,仅供参考,但我需要模拟的是 window.open。 (2认同)

Jee*_*Mok 8

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

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
  });
});
Run Code Online (Sandbox Code Playgroud)


Chr*_*rry 8

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
    });
});
Run Code Online (Sandbox Code Playgroud)


Sag*_*ane 7

我直接分配jest.fn()window.open.

window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','__blank')
Run Code Online (Sandbox Code Playgroud)


Poh*_*How 6

我们也可以用它定义globalsetupTests

// setupTests.js
global.open = jest.fn()
Run Code Online (Sandbox Code Playgroud)

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

// yourtest.test.js
it('correct url is called', () => {
    statementService.openStatementsReport(111);
    expect(global.open).toBeCalled();
});
Run Code Online (Sandbox Code Playgroud)


abh*_*ait 5

你可以试试这个:

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();
});
Run Code Online (Sandbox Code Playgroud)


ser*_*inc 5

如果与window.location.href的窗口位置问题类似,则在测试中无法更改。#890,你可以尝试(调整):

delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
Run Code Online (Sandbox Code Playgroud)