Unit Testing a NestJS Service with Mocked Dependencies

Unit Testing a NestJS Service with Mocked Dependencies

I've come across this issue when I wanted to write unit tests for a service class in a Nest.js application.

For instance, imagine you have a service class, MyServiceClass, that depends on a database provider implementing the DatabaseProviderInterface.

@Injectable()
export class MyServiceClass implement SomeServiceInterface {
  constructor(private readonly databaseProvider: DatabaseProviderInterface) {}

  getProcessedData(): string {
    const data = await this.databaseProvider.getData();
    return data
  }
}        

I wanted to write unit tests for this class to test the getProcessedData method. To do this, I needed to create an instance of MyServiceClass to call the method, but I didn’t want to use a real instance of the database provider. Instead, I wanted to use a mock.

How can I achieve that?

There are different options. First, I can manually create a fake database provider that implements the DatabaseProviderInterface:

const mockDatabaseProvider: Mocked<DatabaseProviderInterface> = {
  getData: jest.fn()
}
// we can mock any result for getData calls
mockDatabaseProvider.getData.mockResolvedValue("Fake data")        

I can use this mock when instantiating the service.

const myService = new MyServiceClass(mockDatabaseProvider)        

That was pretty simple, but what if my DatabaseProviderInterface requires implementing many methods that I don’t need for my test?

Mocking every method manually would be tedious.

One workaround is to trick TypeScript into not complaining about unimplemented methods:

const mockDatabaseProvider: Mocked<DatabaseProviderInterface> = {
  getData: jest.fn()
} as unknown as Mocked<DatabaseProviderInterface>        

This works, but it still requires manually mocking all the methods needed for the test.

I wondered if there was a way to automatically create the mock. Using a Proxy, I came up with the following code:

export function createMockInstance<T>(): jest.Mocked<T> {
  const mockedMethods: Record<string | symbol, jest.Mock> = {};

  return new Proxy({}, {
    get(_, methodName) {
      if (!mockedMethods[methodName]) {
        mockedMethods[methodName] = jest.fn();
      }
      return mockedMethods[methodName];
    }
  }) as jest.Mocked<T>;
}        

With this utility, I can easily create mocks for any interface. For example:

const mockDatabaseProvider = createMockInstance<DatabaseProviderInterface>();

// Then, as before, we can mock the result of any method calls this way
mockDatabaseProvider.getData.mockResolvedValue("Fake data")        

要查看或添加评论,请登录

社区洞察

其他会员也浏览了