Mocking ES6 imports is really useful for being able to test modules; however, my usual technique of using a Webpack loader doesn't work with Jest, since Jest is called from Node and doesn't run test code through Webpack. Instead, we have to use Jest's mocking functions to replace the import with a stub file.
This is the component we're going to test - it has a button to be able to select a folder in an Electron app. We want to be able to check that the dialogue was called and that our doSomethingWithPath
function was called with the response from the dialogue.
// components/Picker.js
import React from 'react';
import { remote } from 'electron';</p>
export default class Picker extends React.Component {
handleClick = e => {
e.preventDefault();
const path = remote.dialog.showOpenDialog({
properties: ['openDirectory']
});
doSomethingWithPath(path);
}
render () {
<button onClick={ this.handleClick }>Choose path</button>
}
}
Configure Jest in package.json
. This entry will probably already exist if you have set up Jest; if not, follow the setup instructions on the Jest site.
// package.json
{
"name": "jest-mock-example",
"author": "James Tease",</p>
"jest": {
"moduleNameMapper": {
"electron": "<rootDir>/mocks/electronMock.js"
}
}
}
The key is the moduleNameMapper
(which may already have some entries) - this tells Jest to change the path in which it looks for a module. We're telling it to look in
when it sees import blah from 'electron'
instead of node_modules/electron
, so we can create a file there to stub the functionality.
is a Jest convention which defines the base folder.
Create a mock electron
file in mocks/electronMock.js
which contains the spies and stubs needed:
// mocks/electronMock.js
export const remote = {
dialog: {
// replace the showOpenDialog function with a spy which returns a value
showOpenDialog: jest.fn().mockReturnValue('path/to/output folder')
}
};
This mock file will now be used by Jest in our test, so we can check that we're getting the correct response:
// Picker.test.js
import Picker from 'components/Picker';
import { shallow } from 'enzyme';
// import the dependency we've mocked so we can access the spies
import { remote } from 'electron';
describe('picker', () => {
it('should let you pick a folder', () => {
const component = shallow(<Picker />);
component.find('button').simulate('click', { preventDefault: () => {} });
// now we can check that the mock was called
expect(remote.dialog.showOpenDialog).toHaveBeenCalled();
// and that the mock returned an expected value
expect(doSomethingWithPath).toHaveBeenCalledWith('path/to/output folder');
});
});
Mock Node Modules
You can also manually mock dependencies: I found that Jest often didn't mock node modules properly using the moduleNameMapper
setting, so you can use jest.mock()
instead.
// Picker.test.js
// manually call 'mock' before the imports
jest.mock('child_process', () => ({ exec: jest.fn() }));
import Picker from 'components/Picker';
import { shallow } from 'enzyme';
import { remote } from 'electron';
import { exec } from 'child_process';
describe('picker', () => {
it('should call a shell script', () => {
const component = shallow(<Picker />);
component.find('button').simulate('click', { preventDefault: () => {} });
// now we can check that the mock was called
expect(exec).toHaveBeenCalledWith('ping 8.8.8.8')
});
});
Hooray!