Jest: How to mock one specific method of a class
Edit 05/03/2021
I see a number of people disagree with the below approach, and that's cool. I do have a slight disagreement with @blade's approach, though, in that it actually doesn't test the class because it's using mockImplementation
. If the class changes, the tests will still always pass giving false positives. So here's an example with spyOn
.
// person.js
export default class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
sayMyName() {
return this.first + " " + this.last; // Adjusted to return a value
}
bla() {
return "bla";
}
}
and the test:
import Person from './'
describe('Person class', () => {
const person = new Person('Guy', 'Smiley')
// Spying on the actual methods of the Person class
jest.spyOn(person, 'sayMyName')
jest.spyOn(person, 'bla')
it('should return out the first and last name', () => {
expect(person.sayMyName()).toEqual('Guy Smiley') // deterministic
expect(person.sayMyName).toHaveBeenCalledTimes(1)
});
it('should return bla when blah is called', () => {
expect(person.bla()).toEqual('bla')
expect(person.bla).toHaveBeenCalledTimes(1)
})
});
Cheers! ð»
I don't see how the mocked implementation actually solves anything for you. I think this makes a bit more sense
import Person from "./Person";
describe("Person", () => {
it("should...", () => {
const sayMyName = Person.prototype.sayMyName = jest.fn();
const person = new Person('guy', 'smiley');
const expected = {
first: 'guy',
last: 'smiley'
}
person.sayMyName();
expect(sayMyName).toHaveBeenCalledTimes(1);
expect(person).toEqual(expected);
});
});
Using jest.spyOn()
is the proper Jest way of mocking a single method and leaving the rest be. Actually there are two slightly different approaches to this.
1. Modify the method only in a single object
import Person from "./Person";
test('Modify only instance', () => {
let person = new Person('Lorem', 'Ipsum');
let spy = jest.spyOn(person, 'sayMyName').mockImplementation(() => 'Hello');
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
// unnecessary in this case, putting it here just to illustrate how to "unmock" a method
spy.mockRestore();
});
2. Modify the class itself, so that all the instances are affected
import Person from "./Person";
beforeAll(() => {
jest.spyOn(Person.prototype, 'sayMyName').mockImplementation(() => 'Hello');
});
afterAll(() => {
jest.restoreAllMocks();
});
test('Modify class', () => {
let person = new Person('Lorem', 'Ipsum');
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
});
And for the sake of completeness, this is how you'd mock a static method:
jest.spyOn(Person, 'myStaticMethod').mockImplementation(() => 'blah');
Not really answer the question, but I want to show a use case where you want to mock a dependent class to verify another class.
For example: Foo
depends on Bar
. Internally Foo
created an instance of Bar
. You want to mock Bar
for testing Foo
.
Bar class
class Bar {
public runBar(): string {
return 'Real bar';
}
}
export default Bar;
Foo class
import Bar from './Bar';
class Foo {
private bar: Bar;
constructor() {
this.bar = new Bar();
}
public runFoo(): string {
return 'real foo : ' + this.bar.runBar();
}
}
export default Foo;
The test:
import Foo from './Foo';
import Bar from './Bar';
jest.mock('./Bar');
describe('Foo', () => {
it('should return correct foo', () => {
// As Bar is already mocked,
// we just need to cast it to jest.Mock (for TypeScript) and mock whatever you want
(Bar.prototype.runBar as jest.Mock).mockReturnValue('Mocked bar');
const foo = new Foo();
expect(foo.runFoo()).toBe('real foo : Mocked bar');
});
});
Note: this will not work if you use arrow functions to define methods in your class (as they are difference between instances). Converting it to regular instance method would make it work.
See also jest.requireActual(moduleName)