How to mock a logger dependency of a provider in NestJS?

5k views Asked by At

First of all, I'm not sure if that's even a thing, it's my first time writing unit tests for NestJs.

I am trying to write some unit tests for a controller which has the following dependencies in its constructor:

@Controller('builder/instance')
export class InstanceController {
   private executor: Executor;

   constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
    private stripeService: StripeService,
    private instanceService: InstanceService,
    private organizationService: OrganizationService,
    private executorFactory: ExecutorFactory,
    private socketService: SocketService,
    private auditLogService: AuditLogService,
) {
    this.executor = this.executorFactory.getExecutor();
}

And here's the beginning of my test suite:

describe('InstanceController', () => {
let controller: InstanceController;

const mockStripeService = {};
const mockInstanceService = {};
const mockOrganizationService = {};
const mockExecutorFactory = {};
const mockSocketService = {};
const mockAuditLogService = {};

beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
        // imports: [AccountModule],
        controllers: [InstanceController],
        providers: [
            StripeService,
            InstanceService,
            OrganizationService,
            ExecutorFactory,
            SocketService,
            AuditLogService,
        ],
    })
        .overrideProvider(StripeService)
        .useValue(mockStripeService)
        .overrideProvider(InstanceService)
        .useValue(mockInstanceService)
        .overrideProvider(OrganizationService)
        .useValue(mockOrganizationService)
        .overrideProvider(ExecutorFactory)
        .useValue(mockExecutorFactory)
        .overrideProvider(SocketService)
        .useValue(mockSocketService)
        .overrideProvider(AuditLogService)
        .useValue(mockAuditLogService)
        .compile();
});

it('should be defined', () => {
    expect(controller).toBeDefined();
});

Which fails with the following stack trace:

Cannot find module 'src/winston/winston.constants' from 'account/user/user.service.ts'

Require stack:
  account/user/user.service.ts
  sockets/socket.service.ts
  builder/instance/instance.controller.spec.ts

   5 | import * as argon2 from 'argon2';
   6 | import { JwtService } from '@nestjs/jwt';
>  7 | import { WINSTON_MODULE_PROVIDER } from 'src/winston/winston.constants';
     | ^
   8 | import { Organization } from '../organization/organization.entity';
   9 | import { User } from './user.entity';
  10 | import { ConfigService } from '../../config/config.service';

  at Resolver.resolveModule (../node_modules/jest-resolve/build/resolver.js:324:11)
  at Object.<anonymous> (account/user/user.service.ts:7:1)

From the log, I assume the issue is coming from the provider SocketService, which has in turn a provider UserService, which has in turn the Winston Logger injected like so:

@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger

I tried to add UserService into the providers array in my test suite and add a mock implementation of an empty object which didn't solve the issue. Not sure what else to try from here, do I have to add a mock provider inside the mockSocketService or do I have to add a mock logger?

Any help will be appreciated, let me know if I can provide more code for easier understanding.

UPDATE

Changing paths to relative solved the issue of Winston dependencies in mocked services, but now Jest is complaining about not being able to resolve the Winston dependency in the controller. It's because it is exported from another module, but from reading about it I understand it's a bad practice to import a whole module into a unit test. I tried mocking the Logger like so:

beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
        // imports: [AccountModule],
        controllers: [InstanceController],
        providers: [
            Logger,
        ],
    })
        .overrideProvider(Logger)
        .useValue(mockLogger)
        .compile();
});

But no luck, I also tried mocking Winston like this, with a negative result too.

1

There are 1 answers

1
M_Zarnowski On

SOLVED

I was finally able to figure out how to mock the injected Winston logger. Here's complete code:

describe('InstanceController', () => {
let controller: InstanceController;

const mockStripeService = {};
const mockInstanceService = {};
const mockOrganizationService = {};
const mockExecutorFactory = {};
const mockSocketService = {};
const mockAuditLogService = {};

beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
        // imports: [AccountModule],
        controllers: [InstanceController],
        providers: [
            { provide: WINSTON_MODULE_PROVIDER, useValue: {} },
            StripeService,
            InstanceService,
            OrganizationService,
            ExecutorFactory,
            SocketService,
            AuditLogService,
        ],
    })
        .overrideProvider(StripeService)
        .useValue(mockStripeService)
        .overrideProvider(InstanceService)
        .useValue(mockInstanceService)
        .overrideProvider(OrganizationService)
        .useValue(mockOrganizationService)
        .overrideProvider(ExecutorFactory)
        .useValue(mockExecutorFactory)
        .overrideProvider(SocketService)
        .useValue(mockSocketService)
        .overrideProvider(AuditLogService)
        .useValue(mockAuditLogService)
        .compile();

    controller = module.get<InstanceController>(InstanceController);
});

it('should be defined', () => {
    expect(controller).toBeDefined();
});
});