Mocking JavaScript Class Inner Functions With Jest
I was recently writing unit tests for a project and I wanted to test whether a function in one class had been called by a parent class. Sounds simple right?
I found it a bit trickier than anticipated so thought I’d share my learnings:
Set Up
Firstly, let’s use a simple example of two JavaScript classes:
Class Bar
class Bar {
greetWorld( message ) {
console.log( message );
}
}
export default Bar;
Class Foo
We import bar and call the helloWorld
function when we invoke greetWorld
:
import Bar from './bar';
class Foo {
constructor() {
this.bar = new Bar();
}
helloWorld() {
this.bar.greetWorld( 'Hello World' );
}
}
export default Foo;
Initial attempt at testing helloWorld
We want to verify that the helloWorld
function has called the greetWorld
function in our Bar
class. Here was my initial attempt (note: this doesn’t work):
import Foo from './foo';
import Bar from './bar';
describe( 'helloWorld', () => {
it ( 'should call greetWorld', () => {
const foo = new Foo();
const bar = new Bar();
jest.spyOn( bar, 'greetWorld' );
foo.helloWorld();
expect( bar.greetWorld ).toHaveBeenCalledTimes( 1 );
});
});
Run this and you’ll get:
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
The call from helloWorld
to greetWorld
hasn’t registered.
Correctly testing helloWorld
Looking in the Jest docs there are many ways to mock classes. I wanted a simple inline way of mocking it and here’s how I did it:
import Foo from './foo';
import Bar from './bar';
const mockGreetWorld = jest.fn();
jest.mock( './bar', () => {
return jest.fn().mockImplementation(() => {
return {
greetWorld: mockGreetWorld
};
});
});
describe( 'helloWorld', () => {
it ( 'should call greetWorld', () => {
const foo = new Foo();
const bar = new Bar();
foo.helloWorld();
expect( bar.greetWorld ).toHaveBeenCalledTimes( 1 );
});
});
In this instance, we’ve called jest.mock
with a module factory parameter. We cannot assign greetWorld: jest.fn()
, it will fail:
// This will fail
jest.mock( './bar', () => {
return jest.fn().mockImplementation(() => {
return {
greetWorld: jest.fn()
};
});
});
One of the limitations of using the module factory parameter is that jest.mock
is hoisted to the top of the file so you cannot first define a variale and then use it in the factory. The only exception is made for variables that start with the word mock
, so this will work:
// This will work
const mockGreetWorld = jest.fn();
jest.mock( './bar', () => {
return jest.fn().mockImplementation(() => {
return {
greetWorld: mockGreetWorld
};
});
});
But this will fail:
// This will fail
const barGreetWorld = jest.fn();
jest.mock( './bar', () => {
return jest.fn().mockImplementation(() => {
return {
greetWorld: barGreetWorld
};
});
});
You can read more about this way of mocking classes in the Jest documentation here.