![](/img/trans.png)
[英]jest.mock of ES6 class yields ReferenceError: require is not defined
[英]jest.mock(): How to mock ES6 class default import using factory parameter
我想在我的测试文件中模拟我的 ES6 类导入。
如果被模拟的类有多个使用者,将模拟移动到 __mocks__ 可能是有意义的,这样所有测试都可以共享模拟,但在那之前我想将模拟保留在测试文件中。
jest.mock()
可以模拟导入的模块。 当传递单个参数时:
jest.mock('./my-class.js');
它使用在与模拟文件相邻的 __mocks__ 文件夹中找到的模拟实现,或者创建一个自动模拟。
jest.mock()
接受第二个参数,它是一个模块工厂函数。 对于使用export default
导出的 ES6 类,不清楚这个工厂函数应该返回什么。 是不是:
default
值的对象是返回模拟类实例的对象的函数?文档很模糊:
第二个参数可用于指定正在运行的显式模块工厂,而不是使用 Jest 的自动模拟功能:
我正在努力想出一个工厂定义,当消费者import
类时,它可以充当构造函数。 我不断收到TypeError: _soundPlayer2.default is not a constructor
(例如)。
我尝试避免使用箭头函数(因为它们不能用new
调用)并让工厂返回一个具有default
属性(或没有)的对象。
这是一个例子。 这是行不通的; 所有的测试都抛出TypeError: _soundPlayer2.default is not a constructor
。
正在测试的类: sound-player-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);
}
}
被嘲笑的类: sound-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);
});
我可以将什么作为第二个参数传递给 jest.mock() 以允许示例中的所有测试通过? 如果测试需要修改,那没关系——只要它们仍然测试相同的东西。
感谢@SimenB 在 GitHub 上的反馈,更新了解决方案。
工厂函数必须返回模拟:代替它正在模拟的任何对象的对象。
由于我们正在模拟一个 ES6 类,它是一个带有一些语法糖的函数,那么模拟本身必须是一个函数。 因此传递给jest.mock()
的工厂函数必须返回一个函数; 换句话说,它必须是一个高阶函数。
在上面的代码中,工厂函数返回一个对象。 由于在对象上调用new
失败,它不起作用。
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 is not a constructor 。
不抛出错误很好,但我们可能需要测试是否使用正确的参数调用了我们的构造函数。
为了跟踪对构造函数的调用,我们可以用 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
检查SoundPlayer.mock.calls
类的使用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);
});
如果您仍然遇到TypeError: ...default is not a constructor
并且正在使用 TypeScript,请继续阅读。
TypeScript 正在转换您的 ts 文件,并且您的模块很可能是使用 ES2015s import 导入的。 const soundPlayer = require('./sound-player')
。 因此,创建作为默认导出的类的实例将如下所示: new soundPlayer.default()
。 但是,如果您按照文档的建议模拟该课程。
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return { playSoundFile: mockPlaySoundFile };
});
});
你会得到同样的错误,因为soundPlayer.default
没有指向一个函数。 您的模拟必须返回一个对象,该对象具有指向函数的属性默认值。
jest.mock('./sound-player', () => {
return {
default: jest.fn().mockImplementation(() => {
return {
playSoundFile: mockPlaySoundFile
}
})
}
})
对于命名导入,例如import { OAuth2 } from './oauth'
,在本例中用导入的模块名称OAuth2
替换default
:
jest.mock('./oauth', () => {
return {
OAuth2: ... // mock here
}
})
对于阅读此问题的任何人,我已经设置了一个GitHub 存储库来测试模拟模块和类。 它基于上述答案中描述的原则,但它涵盖了默认导出和命名导出。
如果你定义了一个模拟类,你可以使用类似的东西:
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../path-to-mocked-class/MockedRealClass"
);
return {
...mockedModule,
};
});
代码会做一些事情,比如用 MockedRealClass 之一替换原始 RealClass 的方法和属性定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.