Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest: How to mock one specific method of a class

People also ask

How do you mock a function of a class in Jest?

In order to mock a constructor function, the module factory must return a constructor function. In other words, the module factory must be a function that returns a function - a higher-order function (HOF). Since calls to jest. mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables.

How do you mock a method in the same class?

We can mock runInGround(String location) method inside the PersonTest class as shown below. Instead of using mock(class) here we need to use Mockito. spy() to mock the same class we are testing. Then we can mock the method we want as follows.

How do you mock a method in Jest?

There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.

How do you mock a single function from a module in Jest?

Another approach to mock a particular function from an imported module is to use the jest. spyOn function. The API for this function seems to be exactly what we need for our use case, as it accepts an entire module and the particular export that should be spied on.


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");
});

rather than mocking the class you could extend it like this:

class MockedPerson extends Person {
  sayMyName () {
    return 'Hello'
  }
}
// and then
let person = new MockedPerson();

If you are using Typescript, you can do the following:

Person.prototype.sayMyName = jest.fn().mockImplementationOnce(async () => 
        await 'my name is dev'
);

And in your test, you can do something like this:

const person = new Person();
const res = await person.sayMyName();
expect(res).toEqual('my name is dev');

Hope this helps someone!