JEST标志
假设你有以下文件:
// f.jsexport function b(){
return 'b';
}export function a(){
return b();
}
如果你想要mock b
来测试a
,好的……它并不像看起来那么简单。
初学者的方法
我最初尝试的是:
test('a', () => {
const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
// FAILED!
// expected 'c' got 'b'
})
不行……不会起作用。
这是因为导出的b
确实被mock了,但它不是模块内被a
调用的那个b
。
解决方案1——将模块拆分为不同的文件
如果你将b
移到它自己的文件中:
// b.jsexport function b(){
return 'b';
}// f.jsimport {b} from './b';export function a(){
return b();
}
那么测试将通过:
test('a', () => {
const b = require('./b');
const f = require('./f'); jest.spyOn(b, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})
这看起来是最干净的解决方案,但如果你想把你的函数保留在同一个文件中怎么办呢?
解决方案2——使用导出调用mocked函数
在调用函数之前添加exports.
// f.jsexport function b(){
return 'b';
}export function a(){
return exports.b();
}
然后测试将通过:
test('a', () => {
const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})
我真的很喜欢这个解决方案,因为你只需要在测试的文件中更改一些微不足道的东西。此外,即使可以避免这个小变化:
你可以使用这个库 babel-plugin-explicit-exports-references 来自动将同一模块中的所有函数添加 exports.
。
请注意,该库非常新,受众非常小。在安全方面要谨慎使用。
值得一提的次优解——导出命名空间对象
你可以创建一个命名空间,将其作为默认对象导出,并使用命名空间调用b
。
这样,当你调用jest.mock
时,它将替换命名空间对象上的b
函数。
// f.jsconst f = {
b(){
return 'b';
}, a(){
return f.b();
}
};export default f;
然后测试将通过:
test('a', () => {
const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})
但是你需要将其作为默认导入导入,而不能将其分解为命名导出。
// won't work:
// import {a} from './f';import f from './f';...f.a();
有点丑陋。
另一种次优解——使用重写库
库babel-plugin-rewire是一个会改变模块内部内容的侵入性库。
它似乎不是一个非常受欢迎的库,而且它实际上对我没有起作用,因为它不支持TypeScript,但这里大致介绍一下它的工作原理——
你不需要改变你正在测试的文件中的任何内容。
// f.jsexport function b(){
return 'b';
}export function a(){
return b();
}
在测试中重写b
函数:
import {a, __set__} from './f';test('a', () => {
const f = require('./f'); // This rewires b to return 'c'
__set__('b', () => 'c'); expect(f.a()).toBe('c');
// PASSED!})
愉快的测试 :)
评论(0)