在Jest中嘲弄全局

时间:2016-11-06 12:39:43

标签: javascript unit-testing dependencies jestjs babel-jest

Jest中有没有办法模拟全局对象,例如navigatorImage *?我已经非常放弃了这一点,并将其留给了一系列可模拟的实用方法。例如:

// Utils.js
export isOnline() {
    return navigator.onLine;
}

测试这个微小的功能很简单,但很苛刻,而且根本不具有确定性。我可以在那里获得75%的路,但这是我可以去的地方:

// Utils.test.js
it('knows if it is online', () => {
    const { isOnline } = require('path/to/Utils');

    expect(() => isOnline()).not.toThrow();
    expect(typeof isOnline()).toBe('boolean');
});

另一方面,如果我对这个间接是好的,我现在可以通过这些工具访问navigator

// Foo.js
import { isOnline } from './Utils';

export default class Foo {
    doSomethingOnline() {
        if (!isOnline()) throw new Error('Not online');

        /* More implementation */            
    }
}

......并确定性地测试......

// Foo.test.js
it('throws when offline', () => {
    const Utils = require('../services/Utils');
    Utils.isOnline = jest.fn(() => isOnline);

    const Foo = require('../path/to/Foo').default;
    let foo = new Foo();

    // User is offline -- should fail
    let isOnline = false;
    expect(() => foo.doSomethingOnline()).toThrow();

    // User is online -- should be okay
    isOnline = true;
    expect(() => foo.doSomethingOnline()).not.toThrow();
});

在我使用过的所有测试框架中,Jest感觉就像是最完整的解决方案,但是只要我编写笨拙的代码以使其可测试,我觉得我的测试工具让我失望。

这是唯一的解决方案还是我需要添加Rewire?

*不要傻笑。 Image非常适合ping远程网络资源。

6 个答案:

答案 0 :(得分:62)

当每个测试都运行自己的环境时,你可以通过覆盖它们来模拟全局变量。 global名称空间可以访问所有全局变量。

global.navigator = {
  onLine: true
}

覆盖仅对您当前的测试有效,不会影响其他测试。这也是处理Math.randomDate.now

的好方法

注意,通过jsdom中的一些更改,你可能需要像这样模拟全局变量:

Object.defineProperty(globalObject, key, { value, writable: true });

答案 1 :(得分:8)

自从编写了接受的答案以来,Jest可能已经更改,但是在测试之后,Jest似乎没有重置您的全局变量。请参阅附带的测试用例。

https://repl.it/repls/DecentPlushDeals

据我所知,解决此问题的唯一方法是使用afterEach()afterAll()来清理对global的分配。

答案 2 :(得分:4)

执行此操作的正确方法是使用spyOn。这里的其他答案,即使它们起作用,也不要考虑清理并污染全局范围。

// beforeAll
jest
  .spyOn(window, 'navigator', 'get')
  .mockImplementation(() => { ... })

// afterAll
jest.restoreAllMocks();

答案 3 :(得分:1)

如果有人需要使用静态属性模拟全局,那么我的示例应该会有所帮助:

beforeAll(() => {
    global.EventSource = jest.fn().mockImplementation(() => ({
      readyState: 0,
      close: jest.fn()
    }))

    global.EventSource.CONNECTING = 0
    global.EventSource.OPEN = 1
    global.EventSource.CLOSED = 2
  })

答案 4 :(得分:1)

如果您使用的是react-testing-library,并且使用的是库提供的cleanup方法。一旦文件中的所有测试都已运行,它将删除该文件中所做的所有全局声明。这样就不会继续进行任何其他测试。

例如:

import { cleanup } from 'react-testing-library'

afterEach(cleanup)

global.getSelection = () => {

}

describe('test', () => {
  expect(true).toBeTruthy()
})

答案 5 :(得分:0)

如果您需要在 window.navigator 上分配属性的值,那么您需要:

  1. 声明一个非常量变量
  2. 从全局/窗口对象返回它
  3. 更改原始变量的值(通过引用)

这将防止在尝试重新分配 window.navigator 上的值时出错,因为这些值大多是只读的。

let mockUserAgent = "";

beforeAll(() => {
  Object.defineProperty(global.navigator, "userAgent", {
    get() {
      return mockUserAgent;
    },
  });
});

it("returns the newly set attribute", () => {
  mockUserAgent = "secret-agent";
  expect(window.navigator.userAgent).toEqual("secret-agent");
});