在 Jest 中模拟全局变量

问题描述 投票:0回答:7

Jest 中有没有办法模拟全局对象,例如

navigator
Image
*?我几乎放弃了这一点,并将其留给一系列可模拟的实用方法。例如:

// 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 感觉是最完整的解决方案,但每当我编写笨拙的代码只是为了使其可测试时,我都觉得我的测试工具让我失望。

这是唯一的解决方案还是我需要添加重新接线?

*别傻笑。

Image
对于 ping 远程网络资源来说非常有用。

javascript unit-testing dependencies jestjs babel-jest
7个回答
136
投票

由于每个测试套件都运行自己的环境,因此您可以通过覆盖全局变量来模拟它们。所有全局变量都可以通过

global
命名空间访问:

global.navigator = {
  onLine: true
}

覆盖仅影响您当前的测试,不会影响其他测试。这也是处理

Math.random
Date.now
的好方法。

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

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

44
投票

正确的做法是使用

spyOn
。这里的其他答案,即使它们有效,也不要考虑清理和污染全局范围。

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

// afterAll
jest.restoreAllMocks();

23
投票

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

https://repl.it/repls/DecentPlushDeals

据我所知,解决这个问题的唯一方法是使用

afterEach()
afterAll()
来清理你对
global
的作业。

let originalGlobal = global;
afterEach(() => {
  delete global.x;
})

describe('Scope 1', () => {
  it('should assign globals locally', () => {
    global.x = "tomato";
    expect(global.x).toBeTruthy()
  });  
});

describe('Scope 2', () => {
  it('should not remember globals in subsequent test cases', () => {
    expect(global.x).toBeFalsy();
  })
});

10
投票

如果有人需要用 静态属性来模拟 global 那么我的例子应该会有所帮助:

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

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

5
投票

如果您使用

react-testing-library
并且使用库提供的
cleanup
方法,一旦文件中的所有测试运行完毕,它将删除该文件中所做的所有全局声明。这将不会延续到任何其他测试运行。

示例:

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

afterEach(cleanup)

global.getSelection = () => {

}

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

3
投票

如果您需要分配 并重新分配 window.navigator

 上的属性值,那么您需要:

    声明一个非常量变量
  1. 从全局/窗口对象返回它
  2. 更改原始变量的值(通过引用)
这将防止在尝试重新分配

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

0
投票
global.method = jest.fn(() => ...);

这就是现在对我有用的方法。

© www.soinside.com 2019 - 2024. All rights reserved.