在 Jest 中模拟全局变量
Mocking globals in Jest
在 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 感觉是最完整的解决方案,但每当我编写笨拙的代码只是为了使其可测试时,我觉得我的测试工具让我失望。
这是唯一的解决方案还是我需要添加 Rewire?
*别傻笑。 Image
非常适合 ping 远程网络资源。
由于每个 测试套件 运行 都有自己的环境,您可以通过覆盖它们来模拟全局变量。所有全局变量都可以通过 global
命名空间访问:
global.navigator = {
onLine: true
}
覆盖仅对您当前的测试有效,不会影响其他测试。这也是处理 Math.random
或 Date.now
.
的好方法
请注意,通过 jsdom 中的一些更改,您可能必须像这样模拟全局变量:
Object.defineProperty(globalObject, key, { value, writable: true });
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();
})
});
如果有人需要模拟具有 静态属性 的 全局 那么我的示例应该有所帮助:
beforeAll(() => {
global.EventSource = jest.fn(() => ({
readyState: 0,
close: jest.fn()
}))
global.EventSource.CONNECTING = 0
global.EventSource.OPEN = 1
global.EventSource.CLOSED = 2
})
如果您正在使用 react-testing-library
并且您使用库提供的 cleanup
方法,一旦文件中的所有测试都具有 运行.这将不会转移到任何其他测试 运行.
示例:
import { cleanup } from 'react-testing-library'
afterEach(cleanup)
global.getSelection = () => {
}
describe('test', () => {
expect(true).toBeTruthy()
})
正确的做法是使用spyOn
。这里的其他答案,即使它们有效,也不考虑清理并污染全局范围。
// beforeAll
jest
.spyOn(window, 'navigator', 'get')
.mockImplementation(() => { ... })
// afterAll
jest.restoreAllMocks();
如果您需要在 window.navigator
上分配 和 重新分配 属性 的值,那么您需要:
- 声明一个非常量变量
- Return 它来自 global/window 对象
- 更改原始变量的值(通过引用)
这将防止在 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 中有什么方法可以模拟全局对象,例如 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 感觉是最完整的解决方案,但每当我编写笨拙的代码只是为了使其可测试时,我觉得我的测试工具让我失望。
这是唯一的解决方案还是我需要添加 Rewire?
*别傻笑。 Image
非常适合 ping 远程网络资源。
由于每个 测试套件 运行 都有自己的环境,您可以通过覆盖它们来模拟全局变量。所有全局变量都可以通过 global
命名空间访问:
global.navigator = {
onLine: true
}
覆盖仅对您当前的测试有效,不会影响其他测试。这也是处理 Math.random
或 Date.now
.
请注意,通过 jsdom 中的一些更改,您可能必须像这样模拟全局变量:
Object.defineProperty(globalObject, key, { value, writable: true });
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();
})
});
如果有人需要模拟具有 静态属性 的 全局 那么我的示例应该有所帮助:
beforeAll(() => {
global.EventSource = jest.fn(() => ({
readyState: 0,
close: jest.fn()
}))
global.EventSource.CONNECTING = 0
global.EventSource.OPEN = 1
global.EventSource.CLOSED = 2
})
如果您正在使用 react-testing-library
并且您使用库提供的 cleanup
方法,一旦文件中的所有测试都具有 运行.这将不会转移到任何其他测试 运行.
示例:
import { cleanup } from 'react-testing-library'
afterEach(cleanup)
global.getSelection = () => {
}
describe('test', () => {
expect(true).toBeTruthy()
})
正确的做法是使用spyOn
。这里的其他答案,即使它们有效,也不考虑清理并污染全局范围。
// beforeAll
jest
.spyOn(window, 'navigator', 'get')
.mockImplementation(() => { ... })
// afterAll
jest.restoreAllMocks();
如果您需要在 window.navigator
上分配 和 重新分配 属性 的值,那么您需要:
- 声明一个非常量变量
- Return 它来自 global/window 对象
- 更改原始变量的值(通过引用)
这将防止在 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");
});