如何使用@Query 装饰器和验证管道测试 Nestjs 路由?
How to test Nestjs routes with @Query decorators and Validation Pipes?
假设我有一个这样定义的控制器:
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
);
}
}
我希望能够测试请求中发生的情况,例如,如果未提供查询参数。
现在这是我的测试设置:
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;
});
如果我先 nx serve
然后 curl 'localhost:3333/api/ping'
,我得到:
{"message":"Pong!"}
如果我这样做 curl 'localhost:3333/api/news?q=test&pageNumber=1'
我得到:
{"data":['lots of interesting news'],"message":"News fetched successfully!","status":200}
最后,如果我这样做 curl 'localhost:3333/api/news?q=test'
我得到:
{"statusCode":400,"message":["pageNumber should not be empty"],"error":"Bad Request"}
如何复制上一个案例?如果我使用supertest
,则不会像上面那样返回错误。我也没有找到模拟控制器功能的方法。
非常感谢 @jmcdo29 向我解释了如何执行此操作。
代码:
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;
});
解释:
我们需要在 main.ts
中模拟 bootstrap()
的行为。在我的例子中,看起来像这样:
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);
});
}
除了导入 AppModule
,我们还可以像这样配置为测试创建的应用程序:nestApp.useGlobalPipes(new ValidationPipe())
(这需要在 await nestApp.init()
之前完成)
假设我有一个这样定义的控制器:
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
);
}
}
我希望能够测试请求中发生的情况,例如,如果未提供查询参数。
现在这是我的测试设置:
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;
});
如果我先 nx serve
然后 curl 'localhost:3333/api/ping'
,我得到:
{"message":"Pong!"}
如果我这样做 curl 'localhost:3333/api/news?q=test&pageNumber=1'
我得到:
{"data":['lots of interesting news'],"message":"News fetched successfully!","status":200}
最后,如果我这样做 curl 'localhost:3333/api/news?q=test'
我得到:
{"statusCode":400,"message":["pageNumber should not be empty"],"error":"Bad Request"}
如何复制上一个案例?如果我使用supertest
,则不会像上面那样返回错误。我也没有找到模拟控制器功能的方法。
非常感谢 @jmcdo29 向我解释了如何执行此操作。
代码:
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;
});
解释:
我们需要在
main.ts
中模拟bootstrap()
的行为。在我的例子中,看起来像这样: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); }); }
除了导入 AppModule
,我们还可以像这样配置为测试创建的应用程序:nestApp.useGlobalPipes(new ValidationPipe())
(这需要在 await nestApp.init()
之前完成)