简体   繁体   English

在 Jest 中模拟全局变量

[英]Mocking globals in Jest

Is there any way in Jest to mock global objects, such as navigator , or Image *? Jest 中是否有任何方法可以模拟全局对象,例如navigatorImage *? I've pretty much given up on this, and left it up to a series of mockable utility methods.我几乎放弃了这一点,把它留给了一系列可模拟的实用方法。 For example:例如:

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

Testing this tiny function is simple, but crufty and not deterministic at all.测试这个微小的函数很简单,但很粗糙,根本不是确定性的。 I can get 75% of the way there, but this is about as far as I can go:我可以完成 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');
});

On the other hand, if I am okay with this indirection, I can now access navigator via these utilities:另一方面,如果我对这种间接方式没有意见,我现在可以通过这些实用程序访问navigator

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

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

        /* More implementation */            
    }
}

...and deterministically test like this... ...并像这样确定性地进行测试...

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

Out of all the testing frameworks I've used, Jest feels like the most complete solution, but any time I write awkward code just to make it testable, I feel like my testing tools are letting me down.在我使用过的所有测试框架中,Jest 感觉是最完整的解决方案,但是每当我编写笨拙的代码只是为了使其可测试时,我觉得我的测试工具让我失望。

Is this the only solution or do I need to add Rewire?这是唯一的解决方案还是我需要添加重新布线?

*Don't smirk. *别笑。 Image is fantastic for pinging a remote network resource. Image非常适合 ping 远程网络资源。

As every test suite run its own environment, you can mock globals by just overwriting them.由于每个测试套件都运行自己的环境,您可以通过覆盖它们来模拟全局变量。 All global variables can be accessed by the global namespace:所有全局变量都可以通过global命名空间访问:

global.navigator = {
  onLine: true
}

The overwrite has only effects in your current test and will not effect others.覆盖仅对您当前的测试有影响,不会影响其他测试。 This also a good way to handle Math.random or Date.now .这也是处理Math.randomDate.now的好方法。

Note, that through some changes in jsdom it could be possible that you have to mock globals like this:请注意,通过jsdom 中的一些更改,您可能必须像这样模拟全局变量:

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

Jest may have changed since the accepted answer was written, but Jest does not appear to reset your global after testing. Jest 可能在编写接受的答案后发生了变化,但 Jest 似乎并没有在测试后重置您的全局。 Please see the testcases attached.请参阅随附的测试用例。

https://repl.it/repls/DecentPlushDeals https://repl.it/repls/DecentPlushDeals

As far as I know, the only way around this is with an afterEach() or afterAll() to clean up your assignments to global .据我所知,解决这个问题的唯一方法是使用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();
  })
});

The correct way of doing this is to use spyOn .这样做的正确方法是使用spyOn The other answers here, even though they work, don't consider cleanup and pollute the global scope.这里的其他答案,即使它们有效,也不考虑清理和污染全局范围。

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

// afterAll
jest.restoreAllMocks();

If someone needs to mock a global with static properties then my example should help:如果有人需要使用静态属性模拟全局,那么我的示例应该会有所帮助:

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

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

If you are using react-testing-library and you use the cleanup method provided by the library, it will remove all global declarations made in that file once all tests in the file have run.如果您正在使用react-testing-library并使用react-testing-library提供的cleanup方法,则一旦文件中的所有测试都运行,它将删除该文件中所做的所有全局声明。 This will then not carry over to any other tests run.这将不会延续到任何其他测试运行。

Example:示例:

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

afterEach(cleanup)

global.getSelection = () => {

}

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

If you need to assign and reassign the value of a property on window.navigator then you'll need to:如果您需要在window.navigator上分配重新分配属性的值,那么您需要:

  1. Declare a non-constant variable声明一个非常量变量
  2. Return it from the global/window object从全局/窗口对象返回它
  3. Change the value of that original variable (by reference)更改原始变量的值(通过引用)

This will prevent errors when trying to reassign the value on window.navigator because these are mostly read-only.这将防止在尝试重新分配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");
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM