Imagine I have a Controller defined like so:
class NewsEndpointQueryParameters {
@IsNotEmpty()
q: string;
@IsNotEmpty()
pageNumber: number;
}
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get(['', 'ping'])
ping(): PingEndpointResponse {
return this.appService.ping();
}
@Get(['news'])
getNews(
@Query() queryParameters: NewsEndpointQueryParameters
): Observable<NewsEndpointResponse> {
return this.appService.getNews(
queryParameters.q,
queryParameters.pageNumber
);
}
}
I want to be able to test what happens in a request, if, for example, a query parameter is not provided.
Right now this is my testing setup:
describe('AppController', () => {
let app: TestingModule;
let nestApp: INestApplication;
let appService: AppService;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
imports: [HttpModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
describe('/', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/ping', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/ping');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/news', () => {
describe('Correct query', () => {
beforeEach(() => {
const appServiceSpy = jest.spyOn(appService, 'getNews');
appServiceSpy.mockReturnValue(
new Observable<NewsEndpointResponse>((subscriber) => {
subscriber.next({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
subscriber.complete();
})
);
return;
});
test('Returns with a custom body response.', async () => {
const response = await supertest(nestApp.getHttpServer()).get(
'/news?q=test&pageNumber=1'
);
expect(response.body).toStrictEqual({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
return;
});
return;
});
describe('Incorrect query', () => {
test("Returns an error if 'q' query parameter is missing.", async () => {
return;
});
test("Returns an error if 'pageNumber' query parameter is missing.", async () => {
return;
});
return;
});
return;
});
return;
});
If I do nx serve
and then curl 'localhost:3333/api/ping'
, I get:
{"message":"Pong!"}
And if I do curl 'localhost:3333/api/news?q=test&pageNumber=1'
I get:
{"data":['lots of interesting news'],"message":"News fetched successfully!","status":200}
Finally, if I do curl 'localhost:3333/api/news?q=test'
I get:
{"statusCode":400,"message":["pageNumber should not be empty"],"error":"Bad Request"}
How can I replicate the last case? If I use supertest
, there is no error returned like the above. I haven't found a way to mock the Controller's function too.
A very special thank to @jmcdo29 for explaining me how to do this.
Code:
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [
AppService,
{ provide: APP_PIPE, useValue: new ValidationPipe() },
],
imports: [HttpModule, AppModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
Explanation:
We need to model the behavior of bootstrap()
in main.ts
. In my case, in looks like this:
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: environment.nestCors,
});
app.useGlobalPipes(new ValidationPipe());
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3333;
await app.listen(port, () => {
Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix);
});
}
Instead of importing the AppModule
, we could also configure the app created for testing like so: nestApp.useGlobalPipes(new ValidationPipe())
(this needs to be done before await nestApp.init()
)