Mocking ES6 class imports
I'd like to mock my ES6 class imports within my test files.
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.
Jest.mock()
jest.mock()
can mock imported modules. 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.
The module factory parameter
jest.mock()
takes a second argument which is a module factory function. For ES6 classes exported using export default
, it's not clear what this factory function should return. Is it:
- Another function that returns an object that mimics an instance of the class?
- An object that mimics an instance of the class?
- An object with a property
default
that is a function that returns an object that mimics an instance of the class? - A function that returns a higher-order function that itself returns 1, 2 or 3?
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:
I'm struggling to come up with a factory definition that can function as a constructor when the consumer import
s the class. I keep getting TypeError: _soundPlayer2.default is not a constructor
(for example).
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).
Here's an example. This is not working; all of the tests throw TypeError: _soundPlayer2.default is not a constructor
.
Class being tested: 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
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
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? If the tests need to be modified that's okay - as long as they still test for the same things.