Mock a typescript interface with jest

Viewed 47359

Is it possible to mock a typescript interface with jest?

For example:

import { IMultiplier } from "./IMultiplier";

export class Math {
  multiplier: IMultiplier;

  public multiply (a: number, b: number) {
    return this.multiplier.multiply(a, b);
  }
}

Then in a test:

import { Math } from "../src/Math";
import { IMultiplier } from "../src/IMultiplier";

describe("Math", () => {

    it("can multiply", () => {
        let mathlib = new Math();
        mathlib.multiplier = // <--- assign this property a mock
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
    });
});

I've tried to create a mock object to satisfy this a number of ways, but none work. For example assigning it this mock:

let multiplierMock = jest.fn(() => ({ multiply: jest.fn() }));

Will produce something along the lines of:

Error - Type 'Mock<{ multiply: Mock<{}>; }>' is not assignable to type 'IMultiplier'.
4 Answers

The mock just needs to have the same shape as the interface.

(from the docs: One of TypeScript’s core principles is that type-checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”.)

So mathlib.multiplier just needs to be assigned to an object that conforms to IMultiplier.

I'm guessing that IMultiplier from the example looks something like this:

interface IMultiplier {
  multiply(a: number, b: number): number
}

So the example test will work fine by changing the line in question to this:

mathlib.multiplier = {
  multiply: jest.fn((a, b) => a * b)
};

try out moq.ts library.

import {Mock} from "moq.ts";

const multiplier = new Mock<IMultiplier>()
   .setup(instance => instance.multiply(3, 4))
   .returns(12)
   .object();

let mathlib = new Math();
mathlib.multiplier = multiplier;

The answer of @Brian Adams doesn't work if multiplier property is a protected property.
In this case we can do something like this:
Target class:

import { IMultiplier } from "./IMultiplier";

export class Math {
  protected multiplier: IMultiplier;

  public multiply (a: number, b: number) {
    return this.multiplier.multiply(a, b);
  }
}

Unit test:

import { Math } from "../src/Math";
import { IMultiplier } from "../src/IMultiplier";

describe("Math", () => {
    class DummyMultiplier implements IMultiplier {
        public multiply(a, b) {
            // dummy behavior
            return a * b;
        }
    }

    class TestableMathClass extends Math {
        constructor() {
            super();
            // set the multiplier equal to DummyMultiplier
            this.multiplier = new DummyMultiplier();
        }
    }

    it("can multiply", () => {
        // here we use the right TestableMathClass
        let mathlib = new TestableMathClass();
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
    });

    it("can multiply and spy something...", () => {
        // with spy we can verify if the function was called
        const spy = jest
            .spyOn(DummyMultiplier.prototype, 'multiply')
            .mockImplementation((_a, _b) => 0);

        let mathlib = new TestableMathClass();
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
        expect(spy).toBeCalledTimes(1);
    });
});

If you are working with a private property, maybe you can inject the property. So, in unit test you also can create a dummy behavior and inject its.

Related