NestJs failes to compile testing module when running tests with Jest - node.js

I have a CategoryService that is a provider of CategoriesModule:
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: Category.name,
imports: [EventEmitterModule],
inject: [EventEmitter2],
useFactory: (eventEmitter: EventEmitter2) => {
const schema = CategorySchema
schema.post('findOneAndDelete', (category: CategoryDocument) => eventEmitter.emit(CollectionEvents.CategoriesDeleted, [category]))
return schema
}
}
])
],
providers: [CategoriesService]
})
export class CategoriesModule {
}
My CategoriesService is:
#Injectable()
export class CategoriesService {
constructor(#InjectModel(Category.name) private categoryModel: Model<CategoryDocument>) {
}
...
}
Then I have a jest test file of that service categories.service.spec.ts:
describe('CategoriesService', () => {
let service: CategoriesService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CategoriesService]
}).compile()
service = module.get<CategoriesService>(CategoriesService)
})
it('should be defined', () => {
expect(service).toBeDefined()
})
})
But when i run the test (using nestJs built in script test) if failes with this error:
Nest can't resolve dependencies of the CategoriesService (?). Please make sure that the argument CategoryModel at index [0] is available in the RootTestModule context.
Potential solutions:
- If CategoryModel is a provider, is it part of the current RootTestModule?
- If CategoryModel is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing CategoryModel */ ]
})
But I don't get it, when running the server using npm start it all runs ok,
and here it complains about CategoryModel, why?

Category model is used in CategoryService and hence is a dependency for CategoryService. You will need to add the model to your spec file so that the dependency is resolved.
If you are using mongoose look at this answer it will help you.

Related

NestJS Jest error: TypeError: Cannot read properties of undefined (reading '[any variable from required config]')

While trying to cover out project in unit tests using nest's jest I've bumped into a problem of a testing module not being able to pull variables from config.
Basically, I have an EmailService, I want to test it, I use it as a Provider in my testing module. Naturally, as EmailService takes ConfigService in its constructor to pull some variables from config (that initially come from env) I put ConfigService into the providers array as well... well, then upon initialization testing module drops
NestJS Jest error: TypeError: Cannot read properties of undefined (reading 'region')
note: region variable is taken from env in a registered config module
code example of my test that throws
describe('EmailService', () => {
let emailService: EmailService;
let configService: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [EmailService, ConfigService],
}).compile();
emailService = module.get<EmailService>(EmailService);
configService = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(emailService).toBeDefined();
});
});
I have came to the conclusion that it throws an error specifically because EmailService takes ConfigService in it's constructor in this way:
export class EmailService {
private readonly config: IAwsConfig;
private readonly region: IRegion;
constructor(private readonly configService: ConfigService) {
this.config = this.configService.get('aws');
this.region = this.config.region;
}
aditional info: both EmailService and ConfigService work just fine during a normal runtime, it only fails during jest testing
seems like this.configService.get method returns 'undefined' during a test run and i'm, not sure why or how to fix it. Any ideas?
In case you don't want to import the entire ConfigService but just the config values themselves, then you use them in the test as follows :)
// my-config.ts
import { registerAs } from '#nestjs/config';
export default registerAs('myConfig', () => ({ propA: 'aa', propB: 123 }));
import { Inject } from '#nestjs/common';
import { ConfigType } from '#nestjs/config';
import myConfig from './my-config.ts';
export class EmailService {
private propA: string;
private propB: number;
constructor(
#Inject(myConfig.KEY) config: ConfigType<typeof myConfig>
) {
this.propA = config.propA;
this.propB = config.propB;
}
}
import { ConfigModule, registerAs } from '#nestjs/config';
import { Test, TestingModule } from '#nestjs/testing';
describe('Test', () => {
const configValues = { propA: 'aa', proprB: 123 };
const config = registerAs('testConfig', () => configValues);
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forFeature(config)],
providers: [EmailService],
}).compile();
});
});
Was not able to find an answer for 2 hours straight, but then, 10 minutes after asking a question, there you go, an answer.
Seems like ConfigService doesn't provide configs during jest testing so you have to provide it in the testing module with replaced get method, something like such:
providers: [
EmailService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
return hardcodedConfigFromWithinTheTestFile;
}),
},
},
],

NestJS test cannot resolve PinoLogger dependency

On NestJS application startup I am creating few db tables (using TypeORM) and have written service file init-db.service.ts for the same. The service file implements OnApplicationBootstrap interface and onApplicationBootstrap method is called once the application has fully started and is bootstrapped.
I am trying to write a unit test for the service file, but is unable to resolve PinoLogger dependency while creating Testing Module. Please checkout below sample code:
Service file init-db.service.ts:
import {ConfigService} from '#nestjs/config';
import {PinoLogger} from 'nestjs-pino';
import {Injectable, OnApplicationBootstrap} from '#nestjs/common';
#Injectable()
export class InitDB implements OnApplicationBootstrap {
constructor(private readonly logger: PinoLogger, private readonly configService: ConfigService) {}
onApplicationBootstrap() {
this.someMethod();
}
async someMethod(): Promise<void> {
// code to create tables...
}
}
Unit test file init-db.service.spec.ts:
import {ConfigService} from '#nestjs/config';
import {Test, TestingModule} from '#nestjs/testing';
import {InitDB} from './init-db.service';
import {getLoggerToken, PinoLogger} from 'nestjs-pino';
const mockLogger = {
// mock logger functions
};
describe('InitDB', () => {
jest.useFakeTimers();
let configService: ConfigService;
let service: InitDB;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getLoggerToken(InitDB.name), // <-------- PinoLogger dependency
useValue: mockLogger,
},
InitDB,
ConfigService,
],
}).compile();
service = module.get<InitDB>(InitDB);
configService = module.get<ConfigService>(ConfigService);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
Error Message:
Nest can't resolve dependencies of the BootstrapService (?, ConfigService). Please make sure that the argument PinoLogger at index [0] is available in the RootTestModule context.
Potential solutions:
- If PinoLogger is a provider, is it part of the current RootTestModule?
- If PinoLogger is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing PinoLogger */ ]
})
I have already passed PinoLogger at index [0], but the unit test still fails to resolve the dependency.
Please note that you don't use #InjectPinoLogger but you are trying to mock it as if you have used it.
You could either provide PinoLogger as a dependency:
const module: TestingModule = await Test.createTestingModule({
providers: [
PinoLogger,
InitDB,
ConfigService,
],
}).compile();
or use #InjectPinoLogger in your class constructor:
export class InitDB implements OnApplicationBootstrap {
constructor(
#InjectPinoLogger(InitDB.name)
private readonly logger: PinoLogger,
private readonly configService: ConfigService
) {}
}

Nest JS Circular Dependency Issue - Mongoose Module

I am trying to inject dependencies in mongoose module for root async. I want to kind of simulate a cascade delete using mongoose hooks.
I have this module, which imports mongoose module, and it imports other modules.
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
imports: [NotificationModule, UserModule, MuseumModule, ImageModule, CommentModule],
name: Shirt.name,
useFactory: (
NotificationService: NotificationService,
UserService: UserService,
MuseumService: MuseumService,
ImageService: ImageService,
ConfigService: ConfigService,
CommentService: CommentService,
) => {
const schema = ShirtSchema;
schema.post('findOneAndDelete', async function (document: ShirtDocument) {
/* Delete notifications */
if (document) {
await NotificationService.deleteManyFromArray(document._id);
/* Remove shirt from museum */
const shirtUser = await UserService.getById(document.shirtUser.userId);
await MuseumService.removeShirtByMuseumId(shirtUser.museums[0], document._id);
/* Delete images from bucket */
if (document.images && document.images.length > 0) {
document.images.forEach(async (image) => {
if (image.thumbnail) {
await ImageService.deleteImageFromBucketS3({
bucket: ConfigService.get('AWS_THUMBNAIL_BUCKET'),
key: getImageUUID(image.thumbnail),
});
}
await ImageService.deleteImageFromBucketS3({
bucket: ConfigService.get('AWS_BUCKET'),
key: getImageUUID(image.cloudImage),
});
});
}
/* Remove shirt comments */
if (document.comments && document.comments.length > 0) {
await CommentService.deleteManyComments(document.comments.map((c) => c._id));
}
}
});
return schema;
},
inject: [NotificationService, UserService, MuseumService, ImageService, ConfigService, CommentService],
},
]),
UserModule,
TeamModule,
BrandModule,
CountryModule,
MuseumModule,
ImageModule,
],
controllers: [ShirtController],
providers: [ShirtService, ShirtRepository],
exports: [ShirtService],
})
export class ShirtModule {}
I also need to do the same in another module, but when I import the
ShirtModule
the compilation fails with the following error:
Error: Nest cannot create the module instance. Often, this is because
of a circular dependency between modules. Use forwardRef() to avoid
it. Scope [AppModule -> UserModule -> MongooseModule -> MuseumModule
-> MongooseModule -> ShirtModule -> MongooseModule]
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: 'Museum',
imports: [ShirtModule],
useFactory: () => {
const schema = MuseumSchema;
schema.post('findOneAndDelete', async function (document: MuseumDocument) {});
return schema;
},
},
]),
],
controllers: [MuseumController],
providers: [MuseumService, MuseumRepository],
exports: [MuseumService],
})
export class MuseumModule {}
I tried using
forwardRef(() => )
In both modules, but still the same. I can not understand where is the circular dependency and how to solve it.
Please could you help me?. Also, is this is a good approach to use mongoose hooks using nest?
Thanks
Try to use forwardRef(() => MongooseModule.forFeatureAsync(xxxx)). This is work in my case.

Error Cannot find module , testing Nestjs Services

I'm having a hard time with testing services on Nestjs, i believe is something related to my lack of knowledge on how the dependency injection works for tests, weird thing is only getting errors on the test. I have 3 modules Teste, Teste2, Teste3, Teste2 imports Teste3 service, and Teste imports Teste2 service. I tried exporting Teste2 and Teste3, and importing their modules, works fine when i run npm start. Doesnt work on the test thought...
Teste
#Module({
imports: [],
providers: [ TesteService,Teste2Service],
exports: [TesteService],
controllers: [TesteController]
})
export class TesteModule {}
#Injectable()
export class TesteService {
constructor(private teste2Service: Teste2Service){}
teste(){
return this.teste2Service.hello();
}
}
Teste2
#Module({
imports: [Teste3Module],
providers: [Teste2Service],
exports: [Teste2Service]
})
export class Teste2Module {}
#Injectable()
export class Teste2Service {
constructor(private teste3Service: Teste3Service){}
hello(){
return this.teste3Service.hello();
}
}
Teste3
#Module({
providers: [Teste3Service],
exports: [Teste3Service]
})
export class Teste3Module {}
#Injectable()
export class Teste3Service {
hello(){
return 'Hello World';
}
}
the actual test
describe('TesteService', () => {
let service: TesteService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports:[Teste2Module],
providers: [TesteService],
}).compile();
service = module.get<TesteService>(TesteService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
The error
src/teste/teste.service.spec.ts
Cannot find module 'src/teste2/teste2.service' from 'teste.service.ts'
E2E cannot find absolute path.
Change to relative path: ../src/teste2

Nest.js is unable to resolve Mongoose model dependency in unit test

When writing a unit test for a controller, Nest is unable to resolve my Mongoose model dependency:
Nest can't resolve dependencies of the UsersService (?). Please make
sure that the argument USER_MODEL at index [0] is available in the
_RootTestModule context.
Potential solutions:
- If USER_MODEL is a provider, is it part of the current _RootTestModule?
- If USER_MODEL is exported from a separate #Module, is that module imported within _RootTestModule?
#Module({
imports: [ /* the Module containing USER_MODEL */ ]
})
My model is injected via my service constructor in the users.service.ts:
import { IUserModel } from './interfaces';
import { Model } from 'mongoose';
import { USER_MODEL } from './constants/users.constants';
#Injectable()
export class UsersService {
constructor (
#Inject(USER_MODEL)
private readonly userModel: Model<IUserModel>,
) {}
...
}
and my test is defined as:
const mockUserModel = {};
describe('Users Controller', () => {
let usersController: UsersController;
let usersService: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [
{
provide: getModelToken(USER_MODEL),
useValue: mockUserModel,
},
UsersService,
],
}).compile();
usersController = module.get<UsersController>(UsersController);
usersService = module.get<UsersService>(UsersService);
});
it('should define user controller and service', () => {
expect(usersController).toBeDefined();
expect(usersService).toBeDefined();
});
});
All of these classes are defined in the same module. I'm not quite sure what Nest is looking for. I'm following the guide at: https://docs.nestjs.com/fundamentals/testing and have looked through several older Github issues as well.
I've also tried creating a custom class provider as defined here: https://docs.nestjs.com/fundamentals/custom-providers to supply the typed Mongoose Model, but that returned the same error.
Can anyone help me out?
If you are using #Inject(USER_MODEL) then you need to use provide: USER_MODEL in your test. The getModelToken utility method is necessary if you use #InjectModel() instead of the raw #Inject().

Resources