How to mock modules using Jest à la RSpec
January 23, 2018
When I was working on building full stack web applications at Grab, I noticed that even though mocking function calls in Ruby on Rails is rather idiomatic, it is less so for the Javascript ecosystem. Sometimes, we might have some utility functions which makes a external HTTP call which we want to mock out in our tests, but how do we do it in Javascript?
Let’s look at what we want to do in Ruby first before “translating” it to Javascript.
Ruby on Rails applications typically use RSpec as a testing framework where we can easily mock out code.
# http_client.rb
module HTTPClient
def make_http_call
# makes a http call, returns a number
end
end
# module.rb
def add_one_to_http_call
res = make_http_call
res + 1
end
# module_spec.rb
describe 'add_one_to_http_call' do
before do
expect(HTTPClient).to_recieve(:make_http_call).and_return(1)
end
it 'adds one to the response' do
res = add_one_to_http_call
expect(res).to eq(2)
end
end
However, doing the same in Javascript seems to be less obvious, and I had to take some time to figure this out.
Let’s try to write the same code in Javascript. We will use Jest as the test framework for our tests.
// dependency.js
// makeHttpCall is an example function which should not be called, and should be mocked out in
// our tests
export const makeHttpCall = () => {};
export default makeHttpCall;
We will illustrate this example with both default and named exports.
// module.js
import makeHttpCall from 'dependency';
const addOneToHttpCall = () => makeHttpCall() + 1;
export default addOneToHttpCall;
// module.test.js
import addOneToHttpCall from './module';
import makeHttpCall from './dependency';
jest.mock('./dependency');
test('mock is called', () => {
addOneToHttpCall();
expect(makeHttpCall).toHaveBeenCalledTimes(1);
});
By calling jest.mock()
, we can setup what is exported as a mock function.
Similarly, this can be done with named exports.
// module.js
import { makeHttpCall } from './../dependency';
const addOneToHttpCall = () => makeHttpCall() + 1;
export default addOneToHttpCall;
// module.test.js
import addOneToHttpCall from './module';
import { makeHttpCall } from './dependency';
jest.mock('./dependency');
test('mock is called', () => {
addOneToHttpCall();
expect(makeHttpCall).toHaveBeenCalledTimes(1);
});
Note that calling jest.mock(‘./dependency’)
will replace all exported functions of dependency.js
with mock functions.
What if there is another arbitrary named export in dependency.js
, a split()
function that we do not want to mock?
// dependency.js
export const makeHttpCall = () => {};
export const split = (inp, delimiter) => inp.split(delimiter);
export default makeHttpCall;
Simply call jest.mock
with a second argument, using require.requireActual
to retain the value of the original function. Props to Paradite for teaching me how to do this:
// module.test.js
import addOneToHttpCall from './index';
import { makeHttpCall, split } from './../dependency';
jest.mock('./../dependency', () => ({
makeHttpCall: jest.fn(),
split: require.requireActual('./../dependency').split,
}));
test('only mock the value of makeHttpCall()', () => {
makeHttpCall.mockReturnValueOnce(1);
const result = addOneToHttpCall();
expect(result).toEqual(2);
});
test('split() still works as intended', () => {
const result = split('a,b,c', ',');
expect(result).toEqual(['a', 'b', 'c']);
});
Hopefully this short blog post illustrates how mocking in Jest can be just as easy as how we do it in RSpec. Let me know at @jiahaog if you have any feedback regarding this!
Accompanying source code can be found here!
I’m Jia Hao, and I write software in San Francisco. Follow me on Twitter!