typescriptjestjs

Is it possible to automatically mock a typescript class and receive a mocked instance of it instead of the class?


In my application I have some services defined as classes, they have dependencies on instances of other modules like other services or gateways, which are also defined as classes. I'm trying to find the best way to easily mock these class instances to pass as dependency.

For example, I'm testing the service GameFeaturesService(AppContext, GameFeaturesRepository, GameService), which means that I have to create a mocked instance of AppContext, GameFeaturesRepository, and GameService and pass them as dependency to the GameFeaturesService in the test file.

This works but can be very cumbersome because these dependencies have also their own dependencies and I don't want to be creating all these instances in a test file just to be able to create the class I'm trying to test.

My simple solution to that is to create a helper which returns a plain object and casts its type properly, for example:

function getGameServiceMock() {
  return {
    someMethod: jest.fn(),
    anotherMethod: jest.fn(),
  } as unknown as jest.Mocked<GameService>
}

It works perfectly, the type casting gives me the exact type I want and everything works fine, but for example if I modify my GameService I will have to manually go to the mock and modify it as well.

I'm trying to look for a more automatic way to do this, like for example using jest.mock('path/to/service'). This would be perfect but it actually doesn't work because it mocks the entire class and not the instance of the class, so I cannot pass this mock to the constructor of GameFeaturesService because it will be a compile error. The mocked class looks something like this:

[Function: GameService] {
  _isMockFunction: true,
  getMockImplementation: [Function (anonymous)],
  // bunch of other mock functions but no methods of GameService
}

Another small problem is that for example the GameRepository is an interface, so in order to do such automatic mock I would need an implementation. It's not a big deal in my case but in general I think that could be problematic and I'm not even sure if it would be possible to create this mock from an interface.

Is there any way to make this mock creation more automated and out of the box? If not auto of the box at least some way to automate it.

Thanks!


Solution

  • You're probably best off using a library like jest-mock-extended. Out of the box you can do something like this example below:

    const mock = mock<GameService>();
    mock.someMethod('do something');
    expect(mock.someMethod).toHaveBeenCalledWith('do something');
    

    You could create a function that returns a proxy for the object and calls jest.fn() every time a new function is accessed. Below is how I'd imagine a simple implementation would work.

    export function createMockProxy<T extends {}>() {
      return new Proxy({} as Record<string | symbol, () => void>, {
        get: (target, prop) => target[prop] ?? (target[prop] = jest.fn())
      }) as T;
    }
    

    A Proxy is just a special object that allows you to intercept any operations. In this case, the get trap is used to return a spy for the corresponding member.

    This can definitely have some problems. For example, there is no way in this implementation to distinguish between a field and a method. It will just blindly create new spies every time a member is accessed.