[Solved] Jest: How to mock one specific method of a class

Let’s suppose I have the following class:

export default class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }
    sayMyName() {
        console.log(this.first + " " + this.last);
    }
    bla() {
        return "bla";
    }
}

Suppose I want to create a mocked class where method ‘sayMyName’ will be mocked and method ‘bla’ will stay as is.

The test I wrote is:

const Person = require("../Person");

jest.mock('../Person', () => {
    return jest.fn().mockImplementation(() => {
        return {sayMyName: () => {
            return 'Hello'
        }};
    });
});


let person = new Person();
test('MyTest', () => {
    expect(person.sayMyName()).toBe("Hello");
    expect(person.bla()).toBe("bla");
})

The first ‘expect’ statement passes, which means that ‘sayMyName’ was mocked successfully. But, the second ‘expect’ fails with the error:

TypeError: person.bla is not a function

I understand that the mocked class erased all methods.
I want to know how to mock a class such that only specific method(s) will be mocked.

Solution #1:

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);
  });
});
Respondent: sesamechicken

Solution #2:

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');
Respondent: blade

Solution #3:

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!

Respondent: Saif Asad

Solution #4:

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

Solution #5:

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

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

Solution #6:

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.

See also jest.requireActual(moduleName)

Respondent: Ninh Pham

Solution #7:

I’ve combined both @sesamechicken and @Billy Reilly answers to create a util function that mock (one or more) specific methods of a class, without definitely impacting the class itself.

/**
* @CrazySynthax class, a tiny bit updated to be able to easily test the mock.
*/
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    sayMyName() {
        return this.first + " " + this.last + this.yourGodDamnRight();
    }

    yourGodDamnRight() {
        return ", you're god damn right";
    }
}

/**
 * Return a new class, with some specific methods mocked.
 *
 * We have to create a new class in order to avoid altering the prototype of the class itself, which would
 * most likely impact other tests.
 *
 * @param Klass: The class to mock
 * @param functionNames: A string or a list of functions names to mock.
 * @returns {Class} a new class.
 */
export function mockSpecificMethods(Klass, functionNames) {
    if (!Array.isArray(functionNames))
        functionNames = [functionNames];

    class MockedKlass extends Klass {
    }

    const functionNamesLenght = functionNames.length;
    for (let index = 0; index < functionNamesLenght; ++index) {
        let name = functionNames[index];
        MockedKlass.prototype[name] = jest.fn();
    };

    return MockedKlass;
}

/**
* Making sure it works
*/
describe('Specific Mocked function', () => {
    it('mocking sayMyName', () => {
        const walter = new (mockSpecificMethods(Person, 'yourGodDamnRight'))('walter', 'white');

        walter.yourGodDamnRight.mockReturnValue(", that's correct"); // yourGodDamnRight is now a classic jest mock;

        expect(walter.sayMyName()).toBe("walter white, that's correct");
        expect(walter.yourGodDamnRight.mock.calls.length).toBe(1);

        // assert that Person is not impacted.
        const saul = new Person('saul', 'goodman');
        expect(saul.sayMyName()).toBe("saul goodman, you're god damn right");
    });
});
Respondent: jolancornevin

The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Leave a Reply

Your email address will not be published.