mmm
mmm

Reputation: 1446

NestJS Mocking Injected Connection Imported from Different Module

I've been getting this error all day long:

Nest can't resolve dependencies of the ClubsService (ClubsApiService, AuthApiService, ClubFollowersRepo, ClubsRepo, ClubPrivacyRepo, ?). Please make sure that the argument DatabaseConnection at index [5] is available in the ClubsModule context.

Potential solutions:
- If DatabaseConnection is a provider, is it part of the current ClubsModule?
- If DatabaseConnection is exported from a separate @Module, is that module imported within ClubsModule?
  @Module({
    imports: [ /* the Module containing DatabaseConnection */ ]
  })

I figured that the problem is, that I have not mocked the Mongo DB connection. The error is quite clear, the @InjectConnection in ClubsService should be mocked (see below).

ClubsService:

@Injectable()
export class ClubsService {
  constructor(
    private readonly clubsApiService: ClubsApiService,
    private readonly authApiService: AuthApiService,
    private readonly clubFollowersRepo: ClubFollowersRepo,
    private readonly clubsRepo: ClubsRepo,
    private readonly clubPrivacyRepo: ClubPrivacyRepo,
    @InjectConnection() private readonly connection: Connection,   // <--- THIS GUY
  ) {}
// ...
}

The problem is that the test file I am executing is in a different module than where ClubsService is. And so in the different module (let's call it YModule), I have this piece of code:

YModule:

import { getConnectionToken } from '@nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { Connection, connect } from 'mongoose';

describe('YService.spec.ts in YModule', () => {

  beforeAll(async () => {
    mongod = await MongoMemoryServer.create();
    const uri = mongod.getUri();
    mongoConnection = (await connect(uri)).connection;
  });

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
         // ...
      ],
      imports: [ClubsModule],  // <--- ClubsModule is not provider, but imported module
    })
      .overrideProvider(getConnectionToken())
      .useValue(mongoConnection)
      .compile();
  });
});

This approach with getConnectionToken() won't work as I have to mock a connection coming from the imported ClubsModule, not a provider.

How would you mock a connection injected in a different module that you imported?

Thanks a lot! :)

Upvotes: 2

Views: 1457

Answers (1)

mmm
mmm

Reputation: 1446

As Jay McDoniel mentioned in the post comment, you should not import modules in your unit testing file but mock the needed dependencies instead. Why is that? Consider the example from the question above:

ClubsModule has the connection dependency that can be replaced with an in-memory database server, that's true, but this should be done within the ClubsModule itself (clubs folder), not outside in different modules.

What you really want to do outside ClubsModule, let's say, in the YModule (y folder), is to mock every provider ClubsModule exports that you use within the test file of YModule.

This makes sense as you should test ClubsModule specific dependencies only within its module and everywhere else just mock it.

I originally imported ClubsModule because I wanted to use the repository (a provider) that ClubsModule exports. But then I realized that I don't want to test the functionality of the repository's function, I already test them inside the ClubsModule, so there is no need to do that twice. Instead, it is a good idea to mock the repository instead.

Code Example:

y.service.spec.ts:

import { YService } from './y.service';  // <--- For illustration; Provider within YModule
import { ClubsRepo } from '../clubs/clubs.repo'; // <--- Import the real Repository Provider from different Module (ClubsModule)

describe('y.service.spec.ts in YModule', () => {

  const clubsRepo = {       // <--- Mocking ClubRepo's functions used within this test file
    insertMany: () => Promise.resolve([]),
    deleteMany: () => Promise.resolve(),
  }
  
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ClubsRepo,   // <--- The real Repository Provider from the import statement above
      ],
    })
      .overrideProvider(ClubsRepo)  // <--- Overriding the Repository Provider from imports
      .useValue(clubsRepo)  // <--- Overriding to the mock 'clubsRepo' (const above)
      .compile();

    service = module.get<YService>(YService); // <--- For illustration; unlike ClubsRepo, this provider resides within this module 
  });

  it('example', () => {
    // ...
    jest.spyOn(clubsRepo, 'insertMany');  // <--- Using "insertMany" from the mocked clubsRepo (the const) defined at the beginning
    // ...
  });

});

The reason for importing ClubsRepo to the test file y.service.spec.ts is because y.service.ts (the actual Provider in YModule) uses the functions of the ClubsRepo. In this case, don't forget importing ClubsModule in y.module.ts too.

y.module.ts:

import { ClubsModule } from '../clubs/clubs.module';

@Module({
  imports: [
    // ...
    ClubsModule,   // <--- Don't forget this line
    // ...
  ],
  providers: [
    // ...
  ],
})
export class YModule {}

That's it, happy testing! :)

Upvotes: 2

Related Questions