26

I would like to strongly type my jest mocks. To a certain extent, I can make it work but when a class has private properties I'm stuck.

An additional question, when I use mocks (the way I currently do) the return type is of the original type but when I have to access any method added by Jest I have to typecast it so jest.Mock to access a method. Is there a better way to do this? I've tried to work with jest.Mock, jest.Mocked, jest.MockInstance.

If anyone could point me in the right direction that would be great!

class MyTest {
    constructor(private readonly msg: string) {}

    public foo(): string {
        return this.msg;
    }
}

const myTestMock: jest.Mock<MyTest, [string]> = jest.fn<MyTest, [string]>(() => ({
    msg: 'private',
    foo: jest.fn().mockReturnValue('aaa'),
}));
// Results in error:
// Type '{ msg: string; foo: Mock<any, any>; }' is not assignable to type 'MyTest'.
// Property 'msg' is private in type 'MyTest' but not in type '{ msg: string; foo: Mock<any, any>; }'

const myTestMockInstance: MyTest = new myTestMock('a');
console.log(myTestMockInstance.foo()); // --> aaa

// Accessing jest mock methods:
(<jest.Mock>myTestMockInstance).mockClear(); // <-- can this be done without type casting

Dirty workaround:

const myTestMock: jest.Mock<MyTest, [string]> = jest.fn<MyTest, [string]>(
    // Cast to any to satisfy TS
    (): any => ({
        msg: 'private',
        foo: jest.fn().mockReturnValue('aaa'),
    })
);
Lin Du
  • 88,126
  • 95
  • 281
  • 483
Michael
  • 1,201
  • 1
  • 8
  • 15
  • 1
    What's the problem with private properties? IMO you should only mock (and test) the public interface. Have you had a look at this article? https://patrickdesjardins.com/blog/strongly-typed-mock-with-typescript-and-jest – Kim Kern Mar 07 '19 at 13:05
  • 2
    @KimKern The problem is that typescript throws an error because the mock does not contain the private property so my mock does not match the type I'm trying to mock. – Michael Mar 07 '19 at 20:19
  • 1
    Thanks for the link, interesting article although I consider it a work around it might be the best option for now. I would expect jest to already have a way of doing it instead of creating your own alias. – Michael Mar 07 '19 at 20:26
  • Yes, that's definitely only a work around. :/ I'm using it and I also had problems with this approach. But since jest is migrating to typescript for the next major release I hope this will get better. Also see https://github.com/facebook/jest/issues/7832 – Kim Kern Mar 08 '19 at 00:21

3 Answers3

6

There is a library that can help you with strongly typed mocks in Typescript with Jest: jest-mock-extended

I'm not sure you should be accessing private properties of your mock. As Kim Kern says, you should only be interested in the public interface of the dependency of the unit under test.

jest-mock-extended contains a mock() method which returns a MockProxy which allows you to access .mockReturnValue() etc.

import { mock } from "jest-mock-extended";

interface WidgetService {
  listMyWidgets(): { id: string }[];
};

const mockedService = mock<WidgetService>();
mockedService.listMyWidgets.mockReturnValue([{ id: 'widget-1' }]);
jjt
  • 173
  • 4
  • 9
2
  1. There is a helper which makes types: look ts-jest
  1. usually you should test public interface, not an implementation. Instead of testing private methods you could extract that logic to a public interface (maybe to another class that provides the interface)
Yozi
  • 11,435
  • 1
  • 22
  • 25
  • 3
    A worked example with ts-jest is in https://stackoverflow.com/a/63074930/1402988 – Piran Jul 24 '20 at 14:21
  • 1
    Note that ts-jest's mocking function has been incorporated into jest as of ts-jest 28.x https://github.com/kulshekhar/ts-jest/blob/6d87eeab86a4da040f5a64d8f06c4495cb03b908/website/versioned_docs/version-27.1/guides/test-helpers.md – wprl Nov 08 '22 at 22:28
-1

You can use jest.fn() to mock instantiating the class object and omit specifying the type on the variable and let Typescript infer it.

myTestMockInstance will not have the type of MyTest because you are mocking it.

class MyTest {
   constructor(private readonly msg: string) {}

   public foo(): string {
      return this.msg
   }
}

const myTestMock = jest.fn((msg: string) => ({
   msg,
   foo() {
      return this.msg
   }
}))

const myTestMockInstance = new myTestMock('a')
console.log(myTestMockInstance.foo()) // --> a