Typescript + Jest: How to partially mock class instance

Viewed 1740

Consider two classes A and B like this:

class A {
    private b: B;

    public constructor(b: B){
        this.b=b;
    }

    public doSomething(){
        this.b.myMethod();
    }
}

class B {
    public myMethod(){...}
    public someOtherMethod(){...}
}

I would like to Test class A while mocking the behaviour of B.myMethod()

Currently we do this like this:

const bMock: Partial<B> = {
    myMethod: jest.fn(<some mock here>),
}

const sut = new A(bMock as any);

sut.doSomething();

expect(bMock.myMethod).toBeCalled();

What we would like to achieve is a similar result but without having to pass the mock with as any and without having to mock all methods on our own. Having the mock type checked is very important for us as otherwise we will not catch breaking changes in the mocked dependencies with this test.

We already had a look into sinon as well, but in some cases we do not want the constructor of our mocked dependency to be invoked and thus stubbing the object after creation is not an option. Stubbing the whole class causes similar issues like described above.

2 Answers

EDIT: Nowadays we are using jest-mock-extended for better jest compatibility and more features

I found a nice solution by using Substitute.

Only Issue being the missing checks for null/undefined as mentioned in the README. Still better than using as any though.

import Substitute, { SubstituteOf } from '@fluffy-spoon/substitute';

class A {
  private b: B;

  public constructor(b: B) {
    this.b = b;
  }

  public doSomething() {
    return this.b.myMethod();
  }
}

class B {
  public myMethod() {
    return 'realMethod';
  }
  public someOtherMethod() {
    return 'realSomeOtherMethod';
  }
}

let bMock: SubstituteOf<B>;
beforeEach(() => {
  bMock = Substitute.for<B>();
});

test('empty mock', () => {
  const sut = new A(bMock);
  console.log(sut.doSomething()); // Output: '[Function]'
});

test('mock myMethod', () => {
  bMock.myMethod().returns('You got mocked!');
  const sut = new A(bMock);
  console.log(sut.doSomething()); // Output:  'You got mocked!'
});

test('does not compile', () => {
  bMock.myMethod().returns(1337); // Will show compilation error due to incompatible type (string vs. number)
  const sut = new A(bMock);
  console.log(sut.doSomething());
});

test('missing null checks', () => {
  bMock.myMethod().returns(); // Will not complain
  const sut = new A(bMock);
  console.log(sut.doSomething()); // Output 'undefined'
});

What about creating a generic? (you can use Partial as it's in your example but you have to check if you've provided the 'sut.doSomething' implementation before calling it.

class B {
  public myMethod(){}
  public someOtherMethod(){}
}

class A<T extends B> {
  private b: T;

  public constructor(b: T){
      this.b=b;
  }

  public doSomething(){
      this.b.myMethod();
  }
}

const bMock = {myMethod: jest.fn(), someOtherMethod: jest.fn()}
const sut = new A(bMock)
sut.doSomething()
expect(bMock.myMethod).toBeCalled();
Related