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

时间:2017-01-27 01:31:52

标签: javascript mocking jestjs

我需要测试一个在浏览器中打开新标签的功能

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

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

使用Jest,我不知道如何模仿窗口。我尝试使用模拟函数设置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]

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

11 个答案:

答案 0 :(得分:37)

而不是window使用global

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

答案 1 :(得分:7)

在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();
});
  1. 最直接地将值直接分配给全局属性,但可能会触发某些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;
});
  1. 不要直接使用全局变量(需要一些重构)

与其直接使用全局值,不如从另一个文件中导入它更干净,因此使用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}`);
  }
}

答案 2 :(得分:6)

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

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

答案 3 :(得分:5)

我找到了一种简单的方法:删除并替换

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

答案 4 :(得分:4)

我们也可以使用global

中的setupTests来定义它
// setupTests.js
global.open = jest.fn()

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

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

答案 5 :(得分:3)

你可以试试这个:

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

答案 6 :(得分:3)

我直接将svm->predict(sample, pred, cv::ml::StatModel::RAW_OUTPUT); 分配给jest.fn()

window.open

答案 7 :(得分:2)

如果它与https://github.com/facebook/jest/issues/890中的窗口位置问题相似,则可以尝试[调整后]

delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();

答案 8 :(得分:1)

可以测试一下:

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

答案 9 :(得分:0)

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

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

参考:https://jestjs.io/docs/en/configuration#setupfilesafterenv-array

答案 10 :(得分:0)

一种对我有用的方法如下。这种方法使我能够测试一些在浏览器和Node中都可以使用的代码,因为它使我可以将window设置为undefined

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