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")