在玩笑中嘲笑全球化

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

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

* 不要傻笑。 Image对于连接远程网络资源非常有用。

150543 次浏览

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.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.

Note, that through some changes in jsdom it could be possible that you have to mock globals like this:

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. Please see the testcases attached.

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.

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

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

The correct way of doing this is to use 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 you need to assign and reassign the value of a property on window.navigator then you'll need to:

  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.

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