[英]jest.mock(): How to mock ES6 class default import using factory parameter
I'd like to mock my ES6 class imports within my test files.我想在我的测试文件中模拟我的 ES6 类导入。
If the class being mocked has multiple consumers, it may make sense to move the mock into __mocks__, so that all the tests can share the mock, but until then I'd like to keep the mock in the test file.如果被模拟的类有多个使用者,将模拟移动到 __mocks__ 可能是有意义的,这样所有测试都可以共享模拟,但在那之前我想将模拟保留在测试文件中。
jest.mock()
can mock imported modules. jest.mock()
可以模拟导入的模块。 When passed a single argument:当传递单个参数时:
jest.mock('./my-class.js');
it uses the mock implementation found in the __mocks__ folder adjacent to the mocked file, or creates an automatic mock.它使用在与模拟文件相邻的 __mocks__ 文件夹中找到的模拟实现,或者创建一个自动模拟。
jest.mock()
takes a second argument which is a module factory function. jest.mock()
接受第二个参数,它是一个模块工厂函数。 For ES6 classes exported using export default
, it's not clear what this factory function should return.对于使用
export default
导出的 ES6 类,不清楚这个工厂函数应该返回什么。 Is it:是不是:
default
that is a function that returns an object that mimics an instance of the class?default
值的对象是返回模拟类实例的对象的函数? The docs are quite vague: 文档很模糊:
The second argument can be used to specify an explicit module factory that is being run instead of using Jest's automocking feature:
第二个参数可用于指定正在运行的显式模块工厂,而不是使用 Jest 的自动模拟功能:
I'm struggling to come up with a factory definition that can function as a constructor when the consumer import
s the class.我正在努力想出一个工厂定义,当消费者
import
类时,它可以充当构造函数。 I keep getting TypeError: _soundPlayer2.default is not a constructor
(for example).我不断收到
TypeError: _soundPlayer2.default is not a constructor
(例如)。
I've tried avoiding use of arrow functions (since they can't be called with new
) and having the factory return an object that has a default
property (or not).我尝试避免使用箭头函数(因为它们不能用
new
调用)并让工厂返回一个具有default
属性(或没有)的对象。
Here's an example.这是一个例子。 This is not working;
这是行不通的; all of the tests throw
TypeError: _soundPlayer2.default is not a constructor
.所有的测试都抛出
TypeError: _soundPlayer2.default is not a constructor
。
Class being tested: sound-player-consumer.js正在测试的类: 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);
}
}
Class being mocked: sound-player.js被嘲笑的类: sound-player.js
export default class SoundPlayer {
constructor() {
// Stub
this.whatever = 'whatever';
}
playSoundFile(fileName) {
// Stub
console.log('Playing sound file ' + fileName);
}
}
The test file: sound-player-consumer.test.js测试文件: 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);
});
What can I pass as the second arg to jest.mock() that will allow all of the tests in the example pass?我可以将什么作为第二个参数传递给 jest.mock() 以允许示例中的所有测试通过? If the tests need to be modified that's okay - as long as they still test for the same things.
如果测试需要修改,那没关系——只要它们仍然测试相同的东西。
Updated with a solution thanks to feedback from @SimenB on GitHub.感谢@SimenB 在 GitHub 上的反馈,更新了解决方案。
The factory function must return the mock: the object that takes the place of whatever it's mocking.工厂函数必须返回模拟:代替它正在模拟的任何对象的对象。
Since we're mocking an ES6 class, which is a function with some syntactic sugar , then the mock must itself be a function.由于我们正在模拟一个 ES6 类,它是一个带有一些语法糖的函数,那么模拟本身必须是一个函数。 Therefore the factory function passed to
jest.mock()
must return a function;因此传递给
jest.mock()
的工厂函数必须返回一个函数; in other words, it must be a higher-order function.换句话说,它必须是一个高阶函数。
In the code above, the factory function returns an object.在上面的代码中,工厂函数返回一个对象。 Since calling
new
on the object fails, it doesn't work.由于在对象上调用
new
失败,它不起作用。
new
on:new
简单模拟: Here's a simple version that, because it returns a function, will allow calling new
:这是一个简单的版本,因为它返回一个函数,将允许调用
new
:
jest.mock('./sound-player', () => {
return function() {
return { playSoundFile: () => {} };
};
});
Note: Arrow functions won't work注意:箭头函数不起作用
Note that our mock can't be an arrow function because we can't call new on an arrow function in Javascript;请注意,我们的模拟不能是箭头函数,因为我们不能在 Javascript 中对箭头函数调用 new; that's inherent in the language.
这是语言固有的。 So this won't work:
所以这行不通:
jest.mock('./sound-player', () => {
return () => { // Does not work; arrow functions can't be called with new
return { playSoundFile: () => {} };
};
});
This will throw TypeError: _soundPlayer2.default is not a constructor .这将抛出TypeError: _soundPlayer2.default is not a constructor 。
Not throwing errors is all well and good, but we may need to test whether our constructor was called with the correct parameters.不抛出错误很好,但我们可能需要测试是否使用正确的参数调用了我们的构造函数。
In order to track calls to the constructor, we can replace the function returned by the HOF with a Jest mock function.为了跟踪对构造函数的调用,我们可以用 Jest 模拟函数替换 HOF 返回的函数。 We create it with
jest.fn()
, and then we specify its implementation with mockImplementation()
.我们用
jest.fn()
创建它,然后我们用mockImplementation()
指定它的实现。
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { playSoundFile: () => {} };
});
});
This will let us inspect usage of our mocked class, using SoundPlayer.mock.calls
.这将让我们使用
SoundPlayer.mock.calls
检查SoundPlayer.mock.calls
类的使用SoundPlayer.mock.calls
。
Our mocked class will need to provide any member functions ( playSoundFile
in the example) that will be called during our tests, or else we'll get an error for calling a function that doesn't exist.我们的模拟类需要提供在我们的测试期间将被调用的任何成员函数(示例中的
playSoundFile
),否则我们将在调用不存在的函数时出错。 But we'll probably want to also spy on calls to those methods, to ensure that they were called with the expected parameters.但是我们可能还想监视对这些方法的调用,以确保使用预期的参数调用它们。
Because a new mock object will be created during our tests, SoundPlayer.playSoundFile.calls
won't help us.因为在我们的测试期间将创建一个新的模拟对象,
SoundPlayer.playSoundFile.calls
不会帮助我们。 To work around this, we populate playSoundFile
with another mock function, and store a reference to that same mock function in our test file, so we can access it during tests.为了解决这个问题,我们用另一个模拟函数填充
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
});
});
Here's how it looks in the test file:这是它在测试文件中的样子:
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);
});
If you are still getting TypeError: ...default is not a constructor
and are using TypeScript keep reading.如果您仍然遇到
TypeError: ...default is not a constructor
并且正在使用 TypeScript,请继续阅读。
TypeScript is transpiling your ts file and your module is likely being imported using ES2015s import. TypeScript 正在转换您的 ts 文件,并且您的模块很可能是使用 ES2015s import 导入的。
const soundPlayer = require('./sound-player')
. const soundPlayer = require('./sound-player')
。 Therefore creating an instance of the class that was exported as a default will look like this: new soundPlayer.default()
.因此,创建作为默认导出的类的实例将如下所示:
new soundPlayer.default()
。 However if you are mocking the class as suggested by the documentation.但是,如果您按照文档的建议模拟该课程。
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return { playSoundFile: mockPlaySoundFile };
});
});
You will get the same error because soundPlayer.default
does not point to a function.你会得到同样的错误,因为
soundPlayer.default
没有指向一个函数。 Your mock has to return an object which has a property default that points to a function.您的模拟必须返回一个对象,该对象具有指向函数的属性默认值。
jest.mock('./sound-player', () => {
return {
default: jest.fn().mockImplementation(() => {
return {
playSoundFile: mockPlaySoundFile
}
})
}
})
For named imports, like import { OAuth2 } from './oauth'
, replace default
with imported module name, OAuth2
in this example:对于命名导入,例如
import { OAuth2 } from './oauth'
,在本例中用导入的模块名称OAuth2
替换default
:
jest.mock('./oauth', () => {
return {
OAuth2: ... // mock here
}
})
For anyone reading this question, I have setup a GitHub repository to test mocking modules and classes.对于阅读此问题的任何人,我已经设置了一个GitHub 存储库来测试模拟模块和类。 It is based on the principles described in the answer above, but it covers both default and named exports.
它基于上述答案中描述的原则,但它涵盖了默认导出和命名导出。
If you have defined a mocking class, you can use something like:如果你定义了一个模拟类,你可以使用类似的东西:
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../path-to-mocked-class/MockedRealClass"
);
return {
...mockedModule,
};
});
The code will do something like replacing method and property definitions of the original RealClass, with the one of MockedRealClass.代码会做一些事情,比如用 MockedRealClass 之一替换原始 RealClass 的方法和属性定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.