在 Jest 中模拟全局变量

Posted

技术标签:

【中文标题】在 Jest 中模拟全局变量【英文标题】:Mocking globals in Jest 【发布时间】:2017-03-19 20:48:41 【问题描述】:

在 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 远程网络资源。

【问题讨论】:

【参考方案1】:

由于每个测试套件都运行自己的环境,您可以通过覆盖它们来模拟全局变量。所有全局变量都可以通过global 命名空间访问:

global.navigator = 
  onLine: true

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

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

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

【讨论】:

global会和浏览器中的window一样吗? 是的,你可以在那里设置东西。但也许不是window 中的所有内容也出现在global 中。这就是我不使用global.navigator.onLine 的原因,因为我不确定global 中是否有navigator 对象。 请注意,作为一般做法,如今并非所有全局属性都是可覆盖的。有些具有可写的 false 并且会忽略值更改尝试。 "覆盖只对您当前的测试有效,不会影响其他测试。" - 这在任何地方都有记录吗? @JamesPlayer 我可以肯定地确认,一个测试中的覆盖影响其他测试。至少在一个测试套件中。【参考方案2】:

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

【讨论】:

我遇到了同样的行为,每次测试运行后我的全局变量都没有重置。在afterEach() 中调用jest.clearAllMocks(); 帮助了我 在 Angular ... import global from '@angular/compiler/src/util' 由于测试可以并行运行,即使在afterEach() 中调用jest.clearAllMocks() 也可能会失败。【参考方案3】:

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

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

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

【讨论】:

【参考方案4】:

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

例子:

import  cleanup  from 'react-testing-library'

afterEach(cleanup)

global.getSelection = () => 



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

【讨论】:

我相信这是 @testing-library/react v9.0 的默认行为,cleanup-after-each 功能在 v10.0 中被删除 -- github.com/testing-library/react-testing-library/releases 你说“一旦文件中的所有测试都运行了”,但你使用afterEach,这是矛盾的【参考方案5】:

这样做的正确方法是使用spyOn。这里的其他答案,即使它们有效,也不要考虑清理和污染全局范围。

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

// afterAll
jest.restoreAllMocks();

【讨论】:

这给了我“Property navigator does not have access type get”——这应该是哪个版本的 Jest? 试试:jest.spyOn('window.navigator', 'get')【参考方案6】:

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

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

这将防止在尝试重新分配 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");
);

【讨论】:

以上是关于在 Jest 中模拟全局变量的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jest 测试时未定义全局节点配置变量

如何在 Mockito 中模拟全局变量

用 Jest 和 Typescript 模拟一个全局对象

如何在 mocha 中模拟全局变量?

模拟全局变量

是否可以在不使用全局变量的 PHP 5.2.x 中模拟闭包?