Binding interceptor to NestJS microservice method - node.js

I just created a simple interceptor to override every error thrown by my application, just like the one on Nest's documentation:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
catchError(err => throwError(() => new ApplicationException())),
);
}
}
And altough exceptions caused by http requests indeed are caught in that interceptor, I just can't make it work with RPC requests (like KafjaJS and events).
Just like the documentation, I've binded it on my app.module:
{
provide: APP_INTERCEPTOR,
useClass: ErrorsInterceptor,
}
I know I'm probably missing something out, can someone clarify where and why what I'm doing is not working and how to make it work?
#Edit: I forgot to mention that I made it work #UseInterceptors() above my controller's method, but I'd like to make it work without it.
#Edit 2: I have a hybrid appplication, this is what my main looks like (as asked by Jay):
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: WinstonModule.createLogger(winstonTransports),
});
app.connectMicroservice<MicroserviceOptions>(kafkaConfig);
const logger = app.get<winston.Logger>(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(logger);
app.enableCors(corsConfig);
await app.startAllMicroservices();
await app.listen(env.PORT);
}

When working with hybrid applications you need to add { inheritAppConfig: true } to the connectMicroservice() method as a second parameter as described in the docs. This means your main.ts should be
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: WinstonModule.createLogger(winstonTransports),
});
app.connectMicroservice<MicroserviceOptions>(kafkaConfig, { inheritAppConfig: true });
const logger = app.get<winston.Logger>(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(logger);
app.enableCors(corsConfig);
await app.startAllMicroservices();
await app.listen(env.PORT);
}

Related

Mock nestjs decorator

I am using a custom Firewall decorator that provides some utility functionality for many of my endpoints (e.g. throttling, authorization etc.) and I want be able to mock this decorator in my endpoints:
#Controller('some')
export class SomeController {
#Firewall() // mock it and check that it's called with correct arguments
async testEndpoint() {
return 'test';
}
}
I want to mock it and check that it's called with the correct parameters, but I can't figure out how I can do this in my test cases:
import * as request from 'supertest';
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { AppModule } from 'src/app.module';
describe('Some Controller', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('some testcase', () => {
// What do I do here to mock my Firewall decorator? // <--- <--- <---
return request(app.getHttpServer()).get('/some/testEndpoint').expect(401);
});
afterAll(async () => {
await app.close();
});
});
If it can help, here is a short version of the Firewall decorator:
import { applyDecorators } from '#nestjs/common';
import { Throttle, SkipThrottle } from '#nestjs/throttler';
export function Firewall(options?: { skipThrottle?: boolean }) {
const { skipThrottle } = options || {
anonymous: false,
};
const decorators = [];
if (skipThrottle) {
decorators.push(SkipThrottle());
} else {
decorators.push(Throttle(10, 10));
}
return applyDecorators(...decorators);
}
I have checked other answers (including this one) but they didn't help.
Thanks in advance for your time!
The #Throttle() and #SkipThrottle() decorators only apply metadata to the controller / controller method they decorate. They don't do anything on their own. Your custom #Firewall() is a utility decorator to combine these into a single decorator for convenience.
If you take a look at the source code of the nestjs/throttler package you'll see it is the #ThrottlerGuard() guard that retrieves this metadata and actually does the throttling.
I suspect you configured this one as a global app guard, so it is applied for all requests.
#Module({
imports: [
ThrottlerModule.forRoot({...}),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}
In your test you need to mock the ThrottlerGuard.
const ThrottlerGuardMock = {
canActivate(ctx) {
const request = ctx.switchToHttp().getRequest();
// mock implementation goes here
// ...
return true;
}
} as ThrottlerGuard;
const module = await Test.createTestModule({
imports: [AppModule]
})
.overrideProvider(ThrottlerGuard)
.useValue(ThrottlerGuardMock) // <-- magic happens here
.compile();
app = moduleRef.createNestApplication();
await app.init();
You could setup some spies, in the mocked guard retrieve the metadata set by the decorators applied by the #Firewall() decorator and then invoke the spies with that metadata. Then you could just verify if the spies were called with the expected values. That would verify that your custom decorator passed down the expected values to the throttle decorators. Actually testing the #ThrottleGuard() decorator is the nestjs/throttler package's responsibility.

Best practise for getting access to the app instance

I'm creating a NestJS app with Firebase cloud functions. I have to use both onRequest and onCreate (Firestore Events) from Firebase in the NestJS application. It's quite straightforward how to address the onRequest events. However, I'm not sure if I'm doing it right when having to do both at the same time. In order for me to pass the onCreate event changes and context to the service layer, I need to get access to the AppService class. To do that I need access to the app instance that has been created. However, I feel like I'm creating two instances of the app (refer to the code). I would like to know if my current my implementation is best to practice or if there is any way it can be improved. Please note I'm a frontend developer so this work is outside my comfort zone. I would like to know the best way to do this, especially if I have to work with more events such as onUpate, onDelete etc.
import { NestFactory } from '#nestjs/core';
import { ExpressAdapter } from '#nestjs/platform-express';
import { AppModule } from './app.module';
import * as express from 'express';
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Express } from 'express-serve-static-core';
import { AppService } from './app.service';
const server = express();
export const createNestServer = async (expressInstance: Express) => {
//FIRST APP INSTANCE
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
return app.init();
};
createNestServer(server)
.then((v) => console.log('Nest Ready'))
.catch((err) => console.error('Nest broken', err));
//exporting all onRequest events
export const api = functions.https.onRequest(server);
//exporting the onUserCreate event
exports.onUserCreate = functions.firestore
.document('users/{docId}')
.onWrite(async (change, context) => {
console.log('Changes are tracked');
//SECOND APP INSTANCE
const app = await NestFactory.create(AppModule);
return app.get(AppService).onCreate(change, context);
});
I assume you have something like a UsersController (and others) that handles retrieving, creating, updating the users as part of the NestJS application. Just pass the Express instance (wrapped by the NestJS adapter) to the Cloud Function HTTPS handler (onRequest). NestJS will take care of setting up the routes.
export const api = functions.https.onRequest(server)
#Controller('users')
export class UsersController {
constructor(private readonly usersService: UserService) {}
#Get(':userId')
async getUser(#Param('userId') id: String) {
// retrieve user
}
#Post()
async create(#Body() createUserDto: CreateUserDto) {
// create a new user
}
...
}
#Module({
controllers: [UsersController],
providers: [AppService, UsersService],
exports: [AppService, UsersService]
})
export class AppModule {}
The AppService is a provider registered in the AppModule. You could extract it from the INestApplication instance.
const server = express();
let appService: AppService;
export const createNestServer = async (expressInstance: Express) => {
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
appService = app.get(AppService);
return app.init();
};
Then you could use it within the onWrite firestore trigger.
Seems like you are trying to do 2 differents tasks with the same NestJS app. Just let the NestJS (or ExpressJS) app handle the HTTPS requests. And for the onCreate, onWrite ... triggers implement a different solution. Perhaps don't rely on NestJS to handle these types of triggers.
NestJS personally feels like overkill, you could just use ExpressJS. You wouldn't have to register the AppService as a provider and jump through hoops.
The following approach has allowed me to solve the multiple instatiation issue of the app instance. Hopefully this is a good and acceptable way to do this. Feedback is more than welcome.
const server = express();
export const createNestServer = async (expressInstance: Express) => {
//FIRST APP INSTANCE
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
return app.init();
};
const main = createNestServer(server);
export const api = functions.https.onRequest(server);
exports.onUserWrite = functions.firestore
.document('users/{docId}')
.onWrite((change, context) =>
main.then((app) => {
return app.get(AppService).onCreate(change, context);
}),
);

NesJS : using an interceptor for HTTP and WS

I created an interceptor to edit data after passing the controller.
It works with HTTP but not with WS.
This is the code of my interceptor :
#Injectable()
export class SignFileInterceptor implements NestInterceptor {
constructor(private fileService: FilesService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(async (data) => {
const paths = getFilesDtoPaths(data);
for (const path of paths) {
const file = get(data, path);
// Returns a promise
const signed = await this.fileService.signFile(file);
set(data, path, signed);
}
return data; // The data is edited and we can return it.
}),
);
}
}
To use it for HTTP, I add the interceptor to the app module :
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: SignFileInterceptor,
}
]
With this, all my HTTP requests are intercepted, and the response is correct.
I want to make the same thing with WS using the same interceptor.
#WebSocketGateway({
cors,
allowEIO3: true,
})
#UseInterceptors(SignFileInterceptor) // Interceptor added HERE
#Injectable()
export class EventsGateway {
constructor() {}
#WebSocketServer()
server!: Server;
#SubscribeMessage('name1')
async handleJoinRoom(
): Promise<string> {
return 'john doe'
}
#SubscribeMessage('name2')
async handleJoinRoom(
): Promise<string> {
return 'john doe 2'
}
}
When a WS is triggered, the code is executed, but the data is returned BEFORE the end of my interceptor execution.
The data is not edited.
I appreciate your help.
Change map to mergeMap or switchMap to handle the async execution of the code. map from RxJS is a synchronous method.
This interceptor works well for HTTP and WS.
Another issue in my project caused the problem.
Sorry for the inconvenience.

How to use value which returned from controller? Testing controllers on NestJs

Controller and method for testing:
import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '#nestjs/common';
#Controller('api/parts')
export class PartController {
constructor(private readonly partsService: partsService) { }
#Get()
public async getParts(#Response() res: any) {
const parts = await this.partsService.findAll();
return res.status(HttpStatus.OK).json(parts);
}
}
And this is unit test which must test getParts method:
describe('PartsController', () => {
let partsController: PartsController;
let partsService: partsService;
beforeEach(async () => {
partsService = new partsService(Part);
partsController= new PartsController(partsService);
});
describe('findAll', () => {
it('should return an array of parts', async () => {
const result = [{ name: 'TestPart' }] as Part[];
jest.spyOn(partsService, 'findAll').mockImplementation(async () => result);
const response = {
json: (body?: any) => {
expect(body).toBe(result);
},
status: (code: number) => response,
};
await partsController.getParts(response);
});
});
});
This test works correctly, but I think this is a bad solution. When I investigated this problem, I saw this option:
const response = {
json: (body?: any) => {},
status: (code: number) => response,
};
expect(await partsController.getParts(response)).toBe(result);
But when I try it my test don't work, cause await partsController.getParts(response) // undefined
So what should I do to make my test look good?
In solution I use: nodeJS sequelize, nestJS, typescript
Alright so I guess your problems lies on the way you instantiate and use your controller & service.
Let NestJs Testing utils do the job for you, like this:
describe('Parts Controller', () => {
let partsController: PartsController;
let partsService: PartsService;
beforeEach(async () => {
// magic happens with the following line
const module = await Test.createTestingModule({
controllers: [
PartsController
],
providers: [
PartsService
//... any other needed import goes here
]
}).compile();
partsService = module.get<PartsService>(PartsService);
partsController = module.get<PartsController>(PartsController);
});
// The next 4 lines are optional and depends on whether you would need to perform these cleanings of the mocks or not after each tests within this describe section
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it('should be defined', () => {
expect(partsController).toBeDefined();
expect(partsService).toBeDefined();
});
describe('findAll', () => {
it('should return an array of parts', async () => {
const result: Part[] = [{ name: 'TestPart' }];
jest.spyOn(partsService, 'findAll').mockImplementation(async (): Promise<Part[]> => Promise.resolve(result));
const response = {
json: (body?: any) => {},
status: (code: number) => HttpStatus.OK,
};
expect(await partsController.getParts(response)).toBe(result);
});
});
});
I haven't tested the code myself so give it a try (not too sure about the response mocking for the Response type in the Parts Controller tho).
Regarding the Parts Controller by the way you should take advantage of the Response type from express though - try to rewrite code as follows:
import { Controller, Get, Response, HttpStatus, Param, Body, Post, Request, Patch, Delete, Res } from '#nestjs/common';
import { Response } from 'express';
#Controller('api/parts')
export class PartController {
constructor(private readonly partsService: partsService) { }
#Get()
public async getParts(#Response() res: Response) { // <= see Response type from express being used here
const parts = await this.partsService.findAll();
return res.status(HttpStatus.OK).json(parts);
}
}
Finally have a look at this section of the nest official documentation, maybe it can give you some insights about what you're trying to achieve:
- Nest testing section
- Nest library approach
In the second link, at the almost beginning of the page, it is stated in the https://docs.nestjs.com/controllers#request-object section the following:
For compatibility with typings across underlying HTTP platforms (e.g., Express and Fastify), Nest provides #Res() and #Response()
decorators. #Res() is simply an alias for #Response(). Both directly
expose the underlying native platform response object interface. When
using them, you should also import the typings for the underlying
library (e.g., #types/express) to take full advantage. Note that when
you inject either #Res() or #Response() in a method handler, you put
Nest into Library-specific mode for that handler, and you become
responsible for managing the response. When doing so, you must issue
some kind of response by making a call on the response object (e.g.,
res.json(...) or res.send(...)), or the HTTP server will hang.
Hope it helps, don't hesitate to comment, or share your solution if it helped finding another one ! :)
Welcome to StackOverflow platform by the way !

How to jest.spyOn only the base class method, not the overridden method

Trying to write test scripts for my nestjs application.
I have controller/service framework, that looks like this:
Controller:
export class MyController {
constructor(
protected _svc: MyService
) {}
#Get()
async getAll(): Promise<Array<Person>> {
return await this._svc.findAll();
}
}
Service:
#Injectable()
export class MyService extends DbService < Person > {
constructor(
private _cache: CacheService
) {
super(...);
}
async findAll() {
return super.findAll().then(res => {
res.map(s => {
this._cache.setValue(`key${s.ref}`, s);
});
return res;
});
}
Base class:
#Injectable()
export abstract class DbService<T> {
constructor() {}
async findAll(): Promise<Array<T>> {
...
}
}
My controller is the entry point when calling an endpoint on the API. This calls the service, which extends the DbService, which is what communicates with my database. There are a lot of services which all extend this DbService. In this case, the MyService class overrides the DbService "findAll" method to do some cache manipulation.
My test script has this:
let myController: MyController;
let myService: MyService;
describe("MyController", async () => {
let spy_findall, spy_cacheset;
beforeAll(() => {
this._cacheService = {
// getValue, setValue, delete methods
};
myService = new MyService(this._cacheService);
myController = new MyController(myService);
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
spy_cacheset = jest.spyOn(this._cacheService, "setValue");
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe("getAll", () => {
it("should return an array of one person", async () => {
await myController.getAll().then(r => {
expect(r).toHaveLength(1);
expect(spy_findall).toBeCalledTimes(1);
expect(spy_cacheset).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
});
Now, obviously the mockImplementation of findAll mocks the "findAll" on MyService, so the test fails because spy_cacheset is never called.
What I would like to do is mock only the base method "findAll" from DbService, so that I maintain the extra functionality that exists in MyService.
Is there a way of doing this without just renaming the methods in MyService, which I would rather avoid doing?
Edited to add:
Thanks to #Jonatan lenco for such a comprehensive reponse, which I have taken on board and implemented.
I have one further question. CacheService, DbService and a whole lot of other stuff (some of which I want to mock, other that I don't) is in an external library project, "shared".
cache.service.ts
export class CacheService {...}
index.ts
export * from "./shared/cache.service"
export * from "./shared/db.service"
export * from "./shared/other.stuff"
....
This is then compiled and included as a package in node_modules.
In the project where I am writing the tests:
import { CacheService, DocumentService, OtherStuff } from "shared";
Can I still use jest.mock() for just the CacheService, without mocking the whole "shared" project?
In this case since you want to spy on an abstract class (DbService), you can spy on the prototype method:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
Also here some recommendations for your unit tests with NestJS and Jest:
Use jest.mock() in order to simplify your mocking (in this case for CacheService). See https://jestjs.io/docs/en/es6-class-mocks#automatic-mock.
When you do jest.spyOn(), you can assert the method execution without the need of the spy object. Instead of:
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
...
expect(spy_findall).toBeCalledTimes(1);
You can do:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
...
expect(DbService.prototype.findAll).toBeCalledTimes(1);
If you are mocking a class properly, you do not need to spy on the method (if you do not want to mock its implementation).
Use the Testing utilities from NestJS, it will help you a lot specially when you have complex dependency injection. See https://docs.nestjs.com/fundamentals/testing#testing-utilities.
Here is an example that applies these 4 recommendations for your unit test:
import { Test } from '#nestjs/testing';
import { CacheService } from './cache.service';
import { DbService } from './db.service';
import { MyController } from './my.controller';
import { MyService } from './my.service';
import { Person } from './person';
jest.mock('./cache.service');
describe('MyController', async () => {
let myController: MyController;
let myService: MyService;
let cacheService: CacheService;
const testPerson = new Person();
beforeAll(async () => {
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
CacheService,
],
}).compile();
myService = module.get<MyService>(MyService);
cacheService = module.get<CacheService>(CacheService);
myController = module.get<MyController>(MyController);
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe('getAll', () => {
it('Should return an array of one person', async () => {
const r = await myController.getAll();
expect(r).toHaveLength(1);
expect(DbService.prototype.findAll).toBeCalledTimes(1);
expect(cacheService.setValue).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
NOTE: for the testing utilities to work and also for your application to work well, you will need to add the #Controller decorator on the class MyController:
import { Controller, Get } from '#nestjs/common';
...
#Controller()
export class MyController {
...
}
About mocking specific items of another package (instead of mocking the whole package) you could do this:
Create a class in your spec file (or you can create it in another file that you import, or even in your shared module) which has a different name but has the same public method names. Note that we use jest.fn() since we do not need to provide an implementation, and that already spies in the method (no need to later do jest.spyOn() unless you have to mock the implementation).
class CacheServiceMock {
setValue = jest.fn();
}
When setting up the providers of your testing module, tell it that you are "providing" the original class but actually providing the mocked one:
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
{ provide: CacheService, useClass: CacheServiceMock },
],
}).compile();
For more info about providers see https://angular.io/guide/dependency-injection-providers (Nest follows the same idea of Angular).

Resources