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();