How to do unit testing to #Inject with ClientKafka in NestJS - nestjs

I need to do some unit test to a kafka implementation in my project with NestJS but I don't know how to do it.
I have a Service thats inject a Client Kafka
export class Service {
private static readonly logger = new Logger(ProducerService.name);
constructor(
#Inject('kafka-registrar') private client: ClientKafka,
private someOtherService: SomeOtherService,
) {}
Module
#Module({
imports: [
ClientsModule.register([
{
name: 'kafka-registrar',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'hero',
brokers: ['localhost:9092'],
},
consumer: {
groupId: '1',
},
},
},
]),
SomeOtherService,
],
providers: [Service],
})
export class Module {}
Unit test
describe('Test Controller', () => {
let clientKafka: ClientKafka;
let someOtherService: SomeOtherService;
let producerService: ProducerService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
ProducerService,
{
provide: SchemaRegistryService,
useValue: {
encodeWithId: jest.fn(),
},
},
{
provide: ClientKafka,
useValue: {
emit: jest.fn(),
},
},
],
}).compile()
clientKafka = moduleRef.get(ClientKafka);
schemaRegistryService = moduleRef.get(SchemaRegistryService);
producerService = moduleRef.get(ProducerService);
});
The project give me this error:
Error: Nest can't resolve dependencies of the ProducerService (?, SchemaRegistryService). Please make sure that the argument kafka-registrar at index [0] is available in the RootTestModule context.
Potential solutions:
- If kafka-registrar is a provider, is it part of the current RootTestModule?
- If kafka-registrar is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing kafka-registrar */ ]
})
I don't know how to resolve this in NestJS. For example in Java,I belive that this can be with #Mock ClientKafka clientKafka bit I dont have any other experience with NestJS... Please helpme! :)

In your test file, you can change provide: ClientKafka to this provide: 'kafka-registrar'.
const moduleRef = await Test.createTestingModule({
providers: [
ProducerService,
{
provide: SchemaRegistryService,
useValue: {
encodeWithId: jest.fn(),
},
},
{
provide: 'kafka-registrar',
useValue: {
emit: jest.fn(),
},
},
],
}).compile()

Related

nestjs providing DataSource dependency not working whit async initialization

I need to use the ConfigService to get the environment variables, but when I use "forRootAsync" of the TypeOrmModule nestjs is unable to resolve the DataService dependency
This is my module
#Module({
imports: [
ConfigModule.forRoot({ validate: validateConfig(AssetEnvironmentVariables), isGlobal: true }),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService): TypeOrmModuleOptions => ({
type: "postgres",
host: configService.get("PG_HOST"),
port: configService.get("PG_PORT"),
database: configService.get("PG_DB_NAME"),
username: configService.get("PG_USER"),
password: configService.get("PG_PASSWORD"),
entities: [AssetEntity],
migrations: ["./migrations/*"],
}),
});
],
providers: [
{ provide: AssetRepository, useClass: AssetPostgresRepository },
],
})
export class AssetModule {}
This is the implementation to AssetRepository
#Injectable()
export class AssetPostgresRepository extends AssetRepository {
private typeOrmRepository: Repository<AssetEntity>;
constructor(dataSource: DataSource) {
super();
this.typeOrmRepository = dataSource.getRepository(AssetEntity);
}
async save(asset: Asset): Promise<void> {
try {
await this.typeOrmRepository.save(asset.toPrimitives());
} catch (e) {
throw new SavingRepositoryException(e);
}
}
}
This is the error that it throw me
ERROR [ExceptionHandler] Nest can't resolve dependencies of the AssetPostgresRepository (?). Please make sure that the argument DataSource at index [0] is available in the AssetModule context.
Potential solutions:
- Is AssetModule a valid NestJS module?
- If DataSource is a provider, is it part of the current AssetModule?
- If DataSource is exported from a separate #Module, is that module imported within AssetModule?
#Module({
imports: [ /* the Module containing DataSource */ ]
})

NestJS - gRPC Client E2E Test

I am trying to write an e2e test for my NestJS microservice that has a gRPC client. No matter what I do to try and mock the ClientsModule in the e2e test it still seems to not pick up the config and is unable to locate the *.proto file.
Please find a sample of my code below:
The clients module in the app.module.ts
// src/app.module.ts
#Module({
imports: [
HttpModule,
...
...
ClientsModule.register([
{
name: 'USERS_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'users',
credentials: credentials.createInsecure(),
protoPath: join(__dirname, '../proto/users.proto'),
url: 'users-grpc-server.users:50051',
},
},
]),
],
controllers: [UsersController],
providers: [UsersService, UsersClient],
})
export class AppModule {}
The users client to make the gRPC call to the gRPC Server NestJS microservice:
// src/clients/users.client.ts
#Injectable()
export class UsersClient implements OnModuleInit {
private readonly logger: Logger = new Logger(UsersClient.name);
private readonly API_KEY: string = this.configService.get<string>('services.v1.api-key');
private usersController: usersController;
constructor(#Inject('USERS_PACKAGE') private client: ClientGrpc, private configService: ConfigService) {}
onModuleInit() {
this.usersController = this.client.getService<UsersController>('UsersController');
}
async getUsers(headers: IncomingHttpHeaders, userId: string): Promise<Users> {
let users: Users = null;
const metadata = new Metadata();
metadata.add('Authorization', headers.authorization);
metadata.add('x-api-key', this.API_KEY);
try {
users = await this.usersController.getUsers({}, metadata);
} catch (e) {
const { message, response: { status = 500 } = {}, stack } = e;
this.logger.error(stack);
throw new HttpException(message, status);
}
return users;
}
}
The update nest-cli.json
{
"collection": "#nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}, {"include": "**/*.proto", "outDir": "./dist"}]
}
}
The e2e test configuration
// test/app.e2e-spec.json
let app: NestFastifyApplication;
let httpService: HttpService;
let cacheManager: Cache;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
HttpModule,
...
...
ClientsModule,
],
})
.overrideProvider(ClientsModule)
.useValue({
name: 'USERS_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'users',
credentials: ServerCredentials.createInsecure(),
protoPath: join(__dirname, '../src/proto/users.proto'),
url: 'localhost:50051',
},
})
.compile();
app = module.createNestApplication(new FastifyAdapter());
await app.init();
await app.getHttpAdapter().getInstance().ready();
httpService = module.get<HttpService>(HttpService);
cacheManager = module.get<Cache>(CACHE_MANAGER);
});
The error:
● Test suite failed to run
The invalid .proto definition (file at "/users-service-grpc-client/app/proto/users.proto" not found)
> 26 | ClientsModule.register([
| ^
27 | {
28 | name: 'USERS_PACKAGE',
29 | transport: Transport.GRPC,
at ClientGrpcProxy.loadProto (node_modules/#nestjs/microservices/client/client-grpc.js:220:39)
at ClientGrpcProxy.createClients (node_modules/#nestjs/microservices/client/client-grpc.js:193:34)
at new ClientGrpcProxy (node_modules/#nestjs/microservices/client/client-grpc.js:29:33)
at Function.create (node_modules/#nestjs/microservices/client/client-proxy-factory.js:27:24)
at node_modules/#nestjs/microservices/module/clients.module.js:12:80
at Array.map (<anonymous>)
at Function.register (node_modules/#nestjs/microservices/module/clients.module.js:10:41)
at Object.<anonymous> (src/app.module.ts:26:19)
It seems like the the e2e test isnt using the ClientsModule configuration from the test and is still using the ClientsModule configuration from the app.module.ts.
Does anyone know of a way to configure this correctly ?
I am not sure how this is working but I changed the ClientsModule in the src/app.module.ts to be registerAsync because I wanted to make the url dynamic and it now seems to be working correctly
// src/app.module.ts
#Module({
imports: [
HttpModule,
...
...
ClientsModule.registerAsync([
{
name: 'USERS_PACKAGE',
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'users',
credentials: credentials.createInsecure(),
protoPath: join(__dirname, '../proto/users.proto'),
url: configService.get<string>('services.users-grpc-server-service.url'),
},
}),
inject: [ConfigService],
},
]),
],
controllers: [UsersController],
providers: [UsersService, UsersClient],
})
export class AppModule {}
Note: In the E2E test, I also had to mock the grpc call using overrideProvider
// test/app.e2e-spec.ts
const module: TestingModule = await Test.createTestingModule({
imports: [
HttpModule,
...
...
ClientsModule,
],
})
.overrideProvider('USERS_PACKAGE')
.useValue({
getService: () => ({
getUsers: jest.fn().mockReturnValue({
"name": "test user",
"age": "55",
"height": "1.89"
}),
}),
})
.compile();

Avoid cyclic dependency in Angular

I would like to use a service in the export function. This gives me a HTTP_INTERCEPTOR error.
How could I use this service without causing the cyclic dependency error. The reason I would like to use a service here is to obtain dynamic values from a rest call.
Module:
export function MSALInstanceFactory(service: AzureService): IPublicClientApplication {
return new PublicClientApplication({
auth: service.getConfiguration(),
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11. Remove this line to use Angular Universal
}
});
}
#NgModule({
declarations: [
AzureComponent
],
exports: [
AzureComponent
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory,
deps: [AzureService]
},
AzureService,
IdentityManagerApiRestService,
]
})
export class AzureModule { }
Service:
#Injectable()
export class AzureService {
constructor(private identityManagerApiRestService: IdentityManagerApiRestService) { }
getConfiguration(): BrowserAuthOptions {
let options: BrowserAuthOptions;
this.identityManagerApiRestService.getAuthenticationConfiguration().subscribe(response => {
options = {
clientId: response.authenticationConfiguration.clientId,
authority: response.authenticationConfiguration.authority,
redirectUri: response.authenticationConfiguration.redirectUri,
}
});
return options;
}
}

NestJS - Use multiple MongoDB connections per module

Is there a way to connect multiple MongoDB connections per module?
app.module.ts
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/masterDB'),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
Similarly, can we define another connection in another module which is a child of app.module?
child.module.ts
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/childDB'),
MongooseModule.forFeature([{ name: 'child', schema: ChildSchema }]),
],
controllers: [ChildController],
providers: [ChildService],
})
export class ChildModule { }
Or any other way to access different databases at once.
Thanks in advance!
[SOLVED March 2021]
Here you'll find the solution:
https://www.learmoreseekmore.com/2020/04/nestjs-multiple-mongodb-databases.html
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { studentSchema } from './schemas/myworld/student.schema';
import { animalSchema } from './schemas/wildlife/animal.schema';
#Module({
imports: [
MongooseModule.forFeature([
{
name: 'Student',
schema: studentSchema,
collection: 'Student',
},
],'myWorldDb'),
MongooseModule.forFeature([
{
name: 'Animals',
schema: animalSchema,
collection: 'Animals'
}
],'wildLifeDb'),
MongooseModule.forRoot(
'mongodb+srv://<userName>:<password>#cluster0-igk.mongodb.net/MyWorld?retryWrites=true&w=majority',
{
connectionName: 'myWorldDb'
}
),
MongooseModule.forRoot(
'mongodb+srv://<username>:<password>#cluster0-igk.mongodb.net/WildLife?retryWrites=true&w=majority',
{
connectionName: 'wildLifeDb'
}
)
],
controllers: [],
providers: [],
})
export class AppModule {}
You have to do it manually you have to use a providers file:
mongoose.providers.ts
import * as mongoose from 'mongoose';
export const mongooseProviders = [
{
provide: 'MASTER_CONNECTION',
useFactory: (): Promise<typeof mongoose> =>
// This mongoose.connect never working for multples DB connection
// mongoose.connect('mongodb://localhost/masterDB'),
// Following is working fine and tested by me
mongoose.createConnection('mongodb://localhost/masterDB'),
},
{
provide: 'CHILD_CONNECTION',
useFactory: (): Promise<typeof mongoose> =>
// This mongoose.connect never working for multples DB connection
// mongoose.connect('mongodb://localhost/masterDB'),
// Following is working fine and tested by me
mongoose.createConnection('mongodb://localhost/ChildDB'),
},
];
mongoose.module.ts
import { Module } from '#nestjs/common';
import { mongooseProviders } from './mongoose.providers';
#Module({
providers: [...mongooseProviders],
exports: [...mongooseProviders],
})
export class MongooseModule {}
model.providers.ts
import { Connection } from 'mongoose';
import { ChildSchema } from './schemas/child/child.schema';
import { MasterSchema } from './schemas/master/master.schema';
export const modelProviders = [
{
provide: 'CHILD_MODEL',
useFactory: (connection: Connection) => connection.model('Child', ChildSchema),
inject: ['CHILD_CONNECTION'],
},
{
provide: 'MASTER_MODEL',
useFactory: (connection: Connection) => connection.model('Master', MasterSchema),
inject: ['MASTER_CONNECTION'],
},
];
And on the constructor instead of using #InjectModel you use #Inject:
#Injectable
export Class ModelService {
constructor(#Inject('MASTER_MODEL') private masterModel: Model<Master>) {}
...
Note: in the module you provide the service you should import the MongooseModule and put as provider modelProviders.
Post above from RalphJS really helped, but it only was using one connection for everything. Had to change 1 thing in model.providers.ts:
instead of mongoose.connect
you have to use mongoose.createConnection
model.providers.ts
import * as mongoose from 'mongoose';
export const mongooseProviders = [
{
provide: 'MASTER_CONNECTION',
useFactory: async (): Promise<unknown> =>
await mongoose.createConnection('mongodb://localhost/masterDB'),
},
{
provide: 'CHILD_CONNECTION',
useFactory: async (): Promise<unknown> =>
await mongoose.createConnection('mongodb://localhost/childDB'),
},
];
https://mongoosejs.com/docs/connections.html#multiple_connections

Nestjs global cache: CacheInterceptor problem

After configured cache globally like the docs, the CacheInterceptor throws an error if i use it outside the app.module.
app.module.ts
const cacheConfig = {
store: redisStore,
host: 'localhost',
port: 6379
}
#Module({
imports: [
CacheModule.register(cacheConfig),
CustomerModule,
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: CacheInterceptor
}
]
})
export class AppModule {}
customer.module.ts
#Module({
imports: [TypeOrmModule.forFeature([CustomerRepository]), TypeOrmModule.forFeature([User])],
controllers: [CustomerController]
})
export class CustomerModule {}
customer.controller.ts
#Controller('customer')
export class CustomerController {
constructor(
#InjectRepository(CustomerRepository) private customerRepository: CustomerRepository,
#InjectRepository(User) private userRepository: Repository<User>
) {}
#Get()
#UseInterceptors(CacheInterceptor)
async get(): Promise<any> {
const user = await this.userRepository.findOne({ where: { id: 1 }, relations: ['customer'] })
console.log(user.customer.name)
const customer = await this.customerRepository.findOne({ where: { id: 1 }, select: ['id', 'name'] })
return { customer: customer.name, email: user.email }
}
}
I would like using the CacheInterceptor along any modules without import the CacheModule each one.
Nest can't resolve dependencies of the APP_INTERCEPTOR (UUID: 6aa42c77-1bac-4098-b217-1b01eb268240) (?, Reflector). Please make sure that the argument at index [0] is available in the CustomerModule context.
If you have { provide: APP_INTERCEPTOR, useClass: CacheInterceptor } you don't need to add in the #UseInterceptors() decorator in your controller. You should have the CahceInterceptor working by default with the rest of the set up

Resources