javascript – jest.mock():如何使用factory参数模拟ES6类的默认导入
作者:互联网
嘲弄ES6类进口
我想在我的测试文件中模拟我的ES6类导入.
如果被模拟的类有多个使用者,那么将模拟移动到__mocks__可能是有意义的,这样所有测试都可以共享模拟,但在此之前我想将模拟保留在测试文件中.
Jest.mock()
jest.mock()可以模拟导入的模块.传递单个参数时:
jest.mock('./my-class.js');
它使用与模拟文件相邻的__mocks__文件夹中的模拟实现,或创建自动模拟.
模块工厂参数
jest.mock()接受第二个参数,它是模块工厂函数.对于使用export default导出的ES6类,不清楚此工厂函数应该返回什么.是吗:
>另一个函数返回一个模仿该类实例的对象?
>模仿类实例的对象?
>具有属性默认值的对象是一个返回模仿该类实例的对象的函数?
>返回高阶函数的函数,该函数本身返回1,2或3?
The docs很模糊:
The second argument can be used to specify an explicit module factory that is being run instead of using Jest’s automocking feature:
我正在努力想出一个工厂定义,当消费者导入类时,它可以作为构造函数.我一直得到TypeError:_soundPlayer2.default不是构造函数(例如).
我已经尝试避免使用箭头函数(因为它们不能用new调用)并让工厂返回一个具有默认属性(或不具有)的对象.
这是一个例子.这不起作用;所有测试抛出TypeError:_soundPlayer2.default不是构造函数.
正在测试的类:
声音播放器,consumer.js
import SoundPlayer from './sound-player'; // Default import
export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer(); //TypeError: _soundPlayer2.default is not a constructor
}
playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}
被嘲笑的类:
声音player.js
export default class SoundPlayer {
constructor() {
// Stub
this.whatever = 'whatever';
}
playSoundFile(fileName) {
// Stub
console.log('Playing sound file ' + fileName);
}
}
测试文件:sound-player-consumer.test.js
import SoundPlayerConsumer from './sound-player-consumer';
import SoundPlayer from './sound-player';
// What can I pass as the second arg here that will
// allow all of the tests below to pass?
jest.mock('./sound-player', function() {
return {
default: function() {
return {
playSoundFile: jest.fn()
};
}
};
});
it('The consumer should be able to call new() on SoundPlayer', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(soundPlayerConsumer).toBeTruthy(); // Constructor ran with no errors
});
it('We can check if the consumer called the mocked class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalled();
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(SoundPlayer.playSoundFile).toHaveBeenCalledWith(coolSoundFileName);
});
我可以将第二个arg传递给jest.mock(),它将允许示例中的所有测试通过?如果测试需要修改,那就没关系 – 只要他们仍然测试相同的东西.
解决方法:
通过feedback from @SimenB on GitHub.更新了解决方案
工厂功能必须返回一个功能
工厂函数必须返回mock:取代它所嘲笑的对象.
因为我们正在模拟一个ES6类,即a function with some syntactic sugar,所以模拟本身必须是一个函数.因此传递给jest.mock()的工厂函数必须返回一个函数;换句话说,它必须是一个更高阶的函数.
在上面的代码中,工厂函数返回一个对象.由于在对象上调用new失败,因此无效.
简单的模拟你可以打电话给新:
这是一个简单的版本,因为它返回一个函数,将允许调用new:
jest.mock('./sound-player', () => {
return function() {
return { playSoundFile: () => {} };
};
});
注意:箭头功能不起作用
请注意,我们的模拟不能是箭头函数,因为我们无法在Javascript中调用箭头函数new;这是语言中固有的.所以这不起作用:
jest.mock('./sound-player', () => {
return () => { // Does not work; arrow functions can't be called with new
return { playSoundFile: () => {} };
};
});
这将抛出TypeError:_soundPlayer2.default不是构造函数.
跟踪使用情况(监视模拟)
不抛出错误都很好,但我们可能需要测试是否使用正确的参数调用了构造函数.
为了跟踪对构造函数的调用,我们可以用一个Jest模拟函数替换HOF返回的函数.我们用jest.fn()
创建它,然后我们用mockImplementation()
指定它的实现.
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { playSoundFile: () => {} };
});
});
这将让我们使用SoundPlayer.mock.calls检查我们的模拟类的用法.
监视我们班级的方法
我们的模拟类将需要提供将在测试期间调用的任何成员函数(示例中为playSoundFile),否则我们将因调用不存在的函数而出错.但我们可能也希望监视对这些方法的调用,以确保使用预期的参数调用它们.
因为在我们的测试中会创建一个新的模拟对象,所以SoundPlayer.playSoundFile.calls将无法帮助我们.为了解决这个问题,我们用另一个模拟函数填充playSoundFile,并在我们的测试文件中存储对同一模拟函数的引用,这样我们就可以在测试期间访问它.
let mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { playSoundFile: mockPlaySoundFile }; // Now we can track calls to playSoundFile
});
});
完整的例子
以下是它在测试文件中的外观:
import SoundPlayerConsumer from './sound-player-consumer';
import SoundPlayer from './sound-player';
let mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return { playSoundFile: mockPlaySoundFile };
});
});
it('The consumer should be able to call new() on SoundPlayer', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(soundPlayerConsumer).toBeTruthy(); // Constructor ran with no errors
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalled();
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});
标签:javascript,ecmascript-6,jestjs 来源: https://codeday.me/bug/20191005/1855421.html