As I work on writing tests in Nest.js, I encountered an error that has been quite challenging to resolve. Despite trying various solutions found online, such as adding Sequelize
to different sections like imports
, exports
, etc., none of them seemed to work.
Nest can't resolve dependencies of the ReportingService (?, MasterMLRepository). Please make sure that the argument Sequelize at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If Sequelize is a provider, is it part of the current RootTestModule?
- If Sequelize is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing Sequelize */ ]
})
Please check out the files below:
reporting.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { PinoLogger } from 'nestjs-pino';
import { MasterML } from './reporting.model';
import { ReportingService } from './reporting.service';
Controller()
export class ReportingController {
constructor(
private readonly reportingService: ReportingService,
private readonly logger: PinoLogger,
) {
this.logger.setContext(ReportingController.name);
}
@Get()
async getMaterialIds(
@Param('plantId') plantId: string,
): Promise<string[] | []> {
try {
return await this.reportingService.getMaterialIds(plantId);
} catch (error) {
this.logger.error(error);
throw error;
}
}
}
reporting.model.ts
import { Column, Model, Table } from 'sequelize-typescript';
@Table({ tableName: 'MASTER_ML' })
export class MasterML extends Model {
@Column({ field: 'BATCH_ID' })
batchId: string;
@Column({ field: 'FEATURE_NAME' })
featureName: string;
@Column({ field: 'FEATURE_VALUE' })
featureValue: string;
@Column({ field: 'MATERIAL_ID' })
materialId: string;
@Column({ field: 'PLANT_ID' })
plantId: string;
@Column({ field: 'STAGE' })
stage: string;
@Column({ field: 'MANUFACTURE_DATE' })
manufactureDate: Date;
}
reporting.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { Sequelize } from 'sequelize-typescript';
import { ReportingController } from './reporting.controller';
import { ReportingService } from './reporting.service';
import { MasterML } from './reporting.model';
@Module({
imports: [SequelizeModule.forFeature([
MasterML,
])],
controllers: [ReportingController],
providers: [ReportingService],
})
export class ReportingModule {}
reporting.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Sequelize } from 'sequelize-typescript';
import { MasterML } from './reporting.model';
@Injectable()
export class ReportingService {
materialIdAttributes: any[] = [
[
this.sequelize.fn('DISTINCT', this.sequelize.col('MATERIAL_ID')),
'materialId',
],
];
materialIdOrder: any[] = [[this.sequelize.literal("'materialId' ASC")]];
constructor(
public sequelize: Sequelize,
@InjectModel(MasterML) private readonly masterMl: typeof MasterML,
) {}
async getMaterialIds(
plantId: string,
): Promise<string[] | []> {
return this.masterMl.findAll({
attributes: this.materialIdAttributes,
where: { plantId },
order: this.materialIdOrder,
}).then((rows) => rows.map((row) => row.materialId));
}
}
reporting.service.spec.ts
import { getModelToken } from '@nestjs/sequelize';
import { Test } from '@nestjs/testing';
import { MasterML } from '../../../src/simply/reporting/reporting.model';
import { ReportingService } from '../../../src/simply/reporting/reporting.service';
const testMaterialIds: string[] = [
'MTRL-1',
'MTRL-2',
'MTRL-3',
];
describe('ReportingService', () => {
let service: ReportingService;
let masterMlModel: typeof MasterML;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
ReportingService,
{
provide: getModelToken(MasterML),
useValue: {
findAll: jest.fn(() => [testMaterialIds]),
},
},
],
}).compile();
service = module.get(ReportingService);
masterMlModel = module.get<typeof MasterML>(getModelToken(MasterML));
});
it('Service should be defined', () => {
expect(service).toBeDefined();
});
it('getMaterialIds - should be defined', async () => {
expect(await service.getMaterialIds('PLNT1')).toBeDefined();
});
it('should get the getMaterialIds', async () => {
expect(await service.getMaterialIds('PLNT1')).toEqual([testMaterialIds]);
const findAllSpy = jest.spyOn(masterMlModel, 'findAll');
expect(findAllSpy).toHaveBeenCalled();
});
});
simply.module.ts
import { Module } from '@nestjs/common';
import { Routes, RouterModule } from '@nestjs/core';
import { Sequelize } from 'sequelize-typescript';
import { PlantModule } from './plant/plant.module';
import { Plant } from './plant/plant.model';
import { ProductModule } from './product/product.module';
import { ProcessModule } from './process/process.module';
import { ReportingModule } from './reporting/reporting.module';
import { Product } from './product/product.model';
import { Process } from './process/process.model';
import { MasterML } from './reporting/reporting.model';
const routes: Routes = [
{
path: '/plants',
module: PlantModule,
children: [
{
path: '/:plantId/products',
module: ProductModule,
children: [
{
path: '/:productId/processes',
module: ProcessModule,
},
],
},
{
path: '/:plantId/reporting',
children: [
{
path: '/materialids',
module: ReportingModule,
},
],
},
],
},
];
@Module({
imports: [
RouterModule.register(routes),
PlantModule,
ProductModule,
ProcessModule,
ReportingModule,
],
})
export class SimplyModule {
constructor(public sequelize: Sequelize) {
this.sequelize.addModels([
Plant,
Product,
Process,
MasterML,
]);
}
}
app.module.ts
import { Module } from '@nestjs/common';
import { LoggerModule, Params } from 'nestjs-pino';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TerminusModule } from '@nestjs/terminus';
import { SequelizeModuleOptions, SequelizeModule } from '@nestjs/sequelize';
import {
apiConfig, logConfig, sequelizeConfig,
} from './config';
import JoiSchema from './config/schema/validation.schema';
import { AuthModule } from './auth/auth.module';
import { TerminusController } from './terminus/terminus.controller';
import { SimplyModule } from './simply/simply.module';
const imports = [
TerminusModule,
ConfigModule.forRoot({
validationSchema: JoiSchema,
validationOptions: {
allowUnknown: true,
abortEarly: false,
},
isGlobal: true,
envFilePath: process.env.NODE_ENV ? `env/.env.${process.env.NODE_ENV}` : 'env/.env.local',
load: [
apiConfig,
logConfig,
sequelizeConfig,
],
}),
LoggerModule.forRootAsync({
useFactory: async (configService: ConfigService) =>
configService.get<Params>('log'),
inject: [ConfigService],
}),
SequelizeModule.forRootAsync({
useFactory: (configService: ConfigService) =>
configService.get<SequelizeModuleOptions>('sequelize'),
inject: [ConfigService],
}),
AuthModule,
SimplyModule,
];
@Module({
imports,
controllers: [TerminusController],
providers: [],
})
export class AppModule { }