Nest.js - applying validation pipe - nestjs

I found the code to apply validation pipe in nest.js. There are two different ways to apply validation pipe globally. I can't figure out the difference between those. What is it? Thanks in advance.
method 1
// app.module.ts
import { APP_PIPE } from '#nestjs/core';
// ...
#Module({
controllers: [AppController],
providers: [
// <-- here
{
provide: APP_PIPE,
useValue: new ValidationPipe({}),
},
]
})
export class AppModule implements NestModule {
// ...
}
method 2
https://docs.nestjs.com/techniques/validation#auto-validation
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({})); // <-- here
await app.listen(3000);
}
bootstrap();

When we are applying a global validation pipe in main.ts we bind the validation pipe throughout the application. But it does miss out on the modules registered from outside the context of the application. On the other hand, when we apply a validation pipe in the module scope the pipe binds on only the routes for that module. This becomes pretty useful when you want to bind specific pipes to a module.

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.

nestjs env variable undefined before class definition

I am working on app written in nestjs and trying to implement WebSocket. One problem i am having is with use of env variables. In the gateway file where i define my WebSocket implementation we have need of using PORT from env before class is defined:
console.log(process.env.WSPORT) // undefined
setInterval(() => {
console.log((process.env.WSPORT) // after few logs its becoming accessible
}, 100)
#WebSocketGateway(process.env.WSPORT)
export class ExportFeedsGateway implements OnGatewayInit {
I tried to debug what is going on, and it seems to be related with when it is invoked as few moments later this variable becomes available for use.
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import { ExportFeedsGateway } from './modules/exportFeeds/export-feeds.gateway';
#Module({
imports: [ConfigModule.forRoot({ expandVariables: true })],
controllers: [AppController],
providers: [AppService, ExportFeedsGateway],
})
export class AppModule {}
export-feeds.gateway.ts
import { OnGatewayInit, WebSocketGateway } from '#nestjs/websockets';
#WebSocketGateway(process.env.WSPORT)
export class ExportFeedsGateway implements OnGatewayInit {
...
}
how to modify this code to make sure that WSPORT is not undefined when its passed to WebSocketGateway decorator?
----- EDIT
I was hoping to utilise nestjs code to access this variable rather then use packages like dotenv
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { WsAdapter } from '#nestjs/platform-ws';
const PORT = process.env.PORT;
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(PORT);
}
bootstrap();
you need to inject that env var in the shell, not via .env. Or use the dotenv by yourself as the first line at your main.ts
I did deeper research and the problem i was having is actually result of trying to use WebSocket implementation (gateway) as standalone import. So the answer and solution to the problem is to create module first
import { Module } from '#nestjs/common';
import { ExportFeedsGateway } from './export-feeds.gateway';
#Module({
providers: [ExportFeedsGateway],
exports: [ExportFeedsGateway],
})
export class ExportFeedsModule {}
This is a proper way of doing things as far as nestjs is concerned.
After this step module should be imported instead of gateway and env variables are accessible through process.env.something
#WebSocketGateway(Number(process.env.EXPORT_FEEDS_PORT))
export class ExportFeedsGateway implements OnGatewayInit {
...
}

How to change/edit a key in ConfigService - Nestjs

I have created a Nestjs server and loading configs using .env file
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`../.env.${process.env.NODE_ENV}`, '../.env'],
}),
I have e2e test cases and need to test a condition on different values for same key in ConfigService
Is there any options to change value of a key?
Credits: Micael Levi
import { createMock, DeepMocked } from '#golevelup/ts-jest';
let mockConfigService: DeepMocked<ConfigService>
let app: INestApplication;
// Before Each
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: createMock<ConfigService>(),
},
],
}).compile();
app = moduleRef.createNestApplication();
// Other configs
await app.init();
mockConfigService = moduleRef.get(ConfigService)
// it(...)
jest.spyOn(mockConfigService, 'get').mockImplementation((key: string) => {
if (key === 'KEY_To_BE_MOCKED') {
return 'true';
} else {
return process.env[key];
}
});
if mocking is a headache you can always go for the "hacky" solution and manually modify the internals of the service object.
example:
config.get('someKey'); // 'original'
config.internalConfig['someKey'] = 'mockValue'; // the hacky act
config.get('someKey'); // 'mockValue'
then you can use the same object and modify it with beforeEach and afterEach or however you see fit in your tests

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

ValidationPipe dont work when use app.useGlobalPipes

Hello. I wanna to use ValidationPipe globaly with useGlobalPipes. I use :
import 'dotenv/config';
import {NestFactory} from '#nestjs/core';
import {ValidationPipe} from '#nestjs/common';
import {AppModule} from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
transform: true,
whitelist: true,
}));
await app.listen(3000);
}
bootstrap();
But this dont work. Work only when I add VAlidationPipe in my controler :
#Post('register')
#UsePipes(new ValidationPipe({ transform: true, whitelist: true}))
async register(#Body() userDTO: RegisterDTO) {
const user = await this.userService.create(userDTO);
const payload: Payload = {
userName: user.userName,
seller: user.seller,
};
const token = await this.authService.signPayload(payload);
return {user, token};
}
Use this construction (await app).useGlobalPipes(new ValidationPipe)
I came across the same issue, I found the solution was on https://docs.nestjs.com/pipes#global-scoped-pipes:
Global pipes are used across the whole application, for every controller and every route handler.
Note that in terms of dependency injection, global pipes registered from outside of any module (with useGlobalPipes() as in the example above) cannot inject dependencies since the binding has been done outside the context of any module. In order to solve this issue, you can set up a global pipe directly from any module using the following construction:
import { Module } from '#nestjs/common';
import { APP_PIPE } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
Having this in your AppModule makes the global validation pipe work.

Resources