Jest: How to mock one specific method of a class
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');
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);
});
});
Have been asking similar question and I think figured out a solution. This should work no matter where Person class instance is actually used.
const Person = require("../Person");
jest.mock("../Person", function () {
const { default: mockRealPerson } = jest.requireActual('../Person');
mockRealPerson.prototype.sayMyName = function () {
return "Hello";
}
return mockRealPerson
});
test('MyTest', () => {
const person = new Person();
expect(person.sayMyName()).toBe("Hello");
expect(person.bla()).toBe("bla");
});