2022.07.26 - [IT] - [Nomad Corders] NestJS로 API 만들기 #3 정리
[Nomad Corders] NestJS로 API 만들기 #3 정리
2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3..
ella-devblog.tistory.com
#4 E2E TESTING
#4.1 Testing movies
e2e 테스트는 movie와 관련된 애플리케이션의 모든 부분을 테스트할 때 사용.
test\app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
//root url test
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
[ROOT URL TEST]
웹사이트의 ROOT URL에 요청을 보내고 'Hello World!'가 나오는지 TEST.
//root url test
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
[>num run test:e2e]
위의 e2e-spec 파일을 수정하지 않고 바로 테스트 해보면, 위처럼 FAIL이 뜸.
왜냐하면 우리가 AppController에서 'Welcome to my Movie API'라고 나오게 수정했기 때문.
따라서, e2e-spec 파일을 .expect('Welcome to my Movie API'); 로 변경해주면 정상적으로 PASS됨.
e2e Testing에서는 Controller에 있는 기능들을 테스트 해볼꺼임.
[GET TEST] : movies에 GET 요청 하는 것을 TEST.
app.getHttpServer() : app Server 주소 가져오기. http://localhost:3000/ 같은걸 안 쓰기 위해서.
//movies get test
it("/movies (GET)", () => {
return request(app.getHttpServer())
.get('/movies')
.expect(200)
.expect([]); //return value array?
});
=> TEST PASS
//movies get test
it("/movies (GET)", () => {
return request(app.getHttpServer())
.get('/movies')
.expect(200)
.expect([{ id: 1 }]); //return value id:1?
});
=> TEST FAIL
지금 movie data가 없기때문에 테스트에 실패함.
다른 것도 테스트하기 위해 describe를 이용하여 코드를 수정함.
//movies url test
describe('/movies', () => {
it('GET', () => { //GET TEST
return request(app.getHttpServer())
.get('/movies')
.expect(200)
.expect([]);
});
});
여기서 it 구문을 추가해 테스트를 추가할 수 있음.
[POST 201 TEST] : 서버에 request해서 movies에 POST할 때 data를 보내면 201을 받는지 TEST.
//movies url test
describe('/movies', () => {
it('POST 201', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
});
=> TEST PASS
[DELETE TEST] : DELETE endpoint까지 앱에 request해서 movies에 delete를 요청하면 404를 반환하는지 TEST.
//movies url test
describe('/movies', () => {
it('DELETE', () => { //DELETE TEST
return request(app.getHttpServer())
.delete('/movies')
.expect(404);
})
});
=> TEST PASS
[여기까지 코드]
//movies url test
describe('/movies', () => {
it('GET', () => { //GET TEST
return request(app.getHttpServer())
.get('/movies')
.expect(200)
.expect([]);
});
it('POST', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
it('DELETE', () => { //DELETE TEST
return request(app.getHttpServer())
.delete('/movies')
.expect(404);
})
});
#4.2 Testing GET movies id
이번에는 테스트하면서 생길법한 에러에 대해 원인이 무엇인지 알아보자!
NestJS는 각 테스트마다 어플리케이션을 생성하고 있음. (실제 서버 어플리케이션이랑은 다름.)
이걸 이해하는 게 왜 중요한 건지 아래에서 설명할꺼임.
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
(참고) 근데 매번 테스트할 때마다 어플리케이션을 만들고 싶지 않고, POST 테스트할 때 만들었던 movie data를 계속 기억하고 싶다. => 그러면, beforeEach를 beforeAll로 바꿔주면 간단하게 해결!
게다가, jest에는 it.todo() 라는 아주 편리한 기능이 있음.
말 그대로 해야할 테스트 목록을 미리 정해놓는 것임.
실행해보면, 연필 모양으로 3가지 해야할 테스트가 있다고 알려줌.
나중에 이것을 실제 테스트 코드로 변경해주면 됨.
[GET 200 TEST]
여기서 GET을 아래처럼 바꾸고 실행해보자.
//movies/id url test
describe('/movies/:id', () => {
it('GET 200', () => {
return request(app.getHttpServer())
.get('/movies/1')
.expect(200);
});
it.todo('DELETE');
it.todo('PATCH');
})
=> TEST FAIL!!
위에서 movie data를 기억하도록 했기때문에,
id가 1인 movie를 GET 하면 정상적으로 가져오고 200이 return 되야하는데,
실행해보면 결과값은 FAIL임!!
음? 이상하지?
그럼 getOne()에 console.log(typeof id);를 추가해서 id의 타입을 알아보자.
[>num run start:dev]로 실제 서버를 작동해서 insomnia에서 id의 타입을 확인하면 number로 정상적임.
근데, [>num run test:e2e]로 테스트 어플리케이션을 작동하면 타입이 string으로 나옴!! 이상하지?
그 이유는, 바로,,
main.ts에서 우리가 옵션으로 등록해줬던 transform:true 값이
실제 서버 작동시에 우리가 입력한 url:/movies/1 의 id 값이 자동으로 number로 형변환이 되기 때문!
근데 e2e 테스트에서는 안 되는 이유가,,
pp.e2e-spec.ts 파일에서 어플리케이션을 생성해주지만, 어떤 pipe에도 올리지 않았음!!!
그.래.서 안 되는거임!!
그러니깐, e2e 테스트나 유닛 테스트를 할 때 이 점을 항상 기억하고,,,
★중요★ 테스팅 환경도 실제 구동 환경의 설정을 그대로 적용시켜줘야 한다! ★중요★
그럼 어떻게 바꿀까?
main.ts에서 pipe 생성하고, option을 줬던 것을 e2e-spec 파일에 동일하게 넣어주면 완성!
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
//pipe 생성
app.useGlobalPipes(new ValidationPipe({
//options
whitelist: true,
forbidNonWhitelisted: true,
//transform : 원래 param or body에서 받는 거는 string인데,
//true로 하면 실제 원하는 값으로 변경해줌.
transform: true,
}));
await app.init();
});
그럼 실행결과 정상적으로 나옴!
[GET 404 TEST] : 200 정상 리턴에 추가로 404 에러까지 TEST
//movies/id url test
describe('/movies/:id', () => {
//it.todo() : 말 그대로 해야할 테스트 목록
it('GET 200', () => {
return request(app.getHttpServer())
.get('/movies/1')
.expect(200);
});
it('GET 404', () => {
return request(app.getHttpServer())
.get('/movies/999')
.expect(404);
});
it.todo('DELETE');
it.todo('PATCH');
})
#4.3 Testing PATCH and DELETE movies id
[PATCH, DELETE TEST] : 해당 id를 가진 movies data의 PATCH, DELETE TEST
//movies/id url test
describe('/movies/:id', () => {
it('PATCH 200', () => {
return request(app.getHttpServer())
.patch('/movies/1')
.send({title:'Updated Test', year:2022, genres:['Updated Test']})
.expect(200);
});
it('PATCH 404', () => {
return request(app.getHttpServer())
.patch('/movies/999')
.send({title:'Updated Test', year:2022, genres:['Updated Test']})
.expect(404);
});
it('DELETE 200', () => {
return request(app.getHttpServer())
.delete('/movies/1')
.expect(200);
});
it('DELETE 404', () => {
return request(app.getHttpServer())
.delete('/movies/999')
.expect(404);
});
[POST 400 TEST] : 잘못된 data를 가진 movie를 POST하는 TEST
//movies url test
describe('/movies', () => {
it('POST 201', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
it('POST 400', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
other: 'thina'
})
.expect(400);
});
});
[최종 app.e2e-spec.ts 코드]
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
//pipe 생성
app.useGlobalPipes(new ValidationPipe({
//options
whitelist: true,
forbidNonWhitelisted: true,
//transform : 원래 param or body에서 받는 거는 string인데,
//true로 하면 실제 원하는 값으로 변경해줌.
transform: true,
}));
await app.init();
});
//root url test
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Welcome to my Movie API');
});
//movies url test
describe('/movies', () => {
it('GET', () => { //GET TEST
return request(app.getHttpServer())
.get('/movies')
.expect(200)
.expect([]);
});
it('POST 201', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
it('POST 400', () => { //POST TEST
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
other: 'thina'
})
.expect(400);
});
it('DELETE', () => { //DELETE TEST
return request(app.getHttpServer())
.delete('/movies')
.expect(404);
})
});
//movies/id url test
describe('/movies/:id', () => {
//it.todo() : 말 그대로 해야할 테스트 목록
it('GET 200', () => {
return request(app.getHttpServer())
.get('/movies/1')
.expect(200);
});
it('GET 404', () => {
return request(app.getHttpServer())
.get('/movies/999')
.expect(404);
});
it('PATCH 200', () => {
return request(app.getHttpServer())
.patch('/movies/1')
.send({title:'Updated Test', year:2022, genres:['Updated Test']})
.expect(200);
});
it('PATCH 404', () => {
return request(app.getHttpServer())
.patch('/movies/999')
.send({title:'Updated Test', year:2022, genres:['Updated Test']})
.expect(404);
});
it('DELETE 200', () => {
return request(app.getHttpServer())
.delete('/movies/1')
.expect(200);
});
it('DELETE 404', () => {
return request(app.getHttpServer())
.delete('/movies/999')
.expect(404);
});
})
});
(참고) HTTP 상태 코드
https://www.whatap.io/ko/blog/40/
HTTP 상태 코드 정리 | 와탭 블로그
HTTP 응답 상태 코드의 목록을 정리하여 소개합니다. 클라이언트의 요청에 따라 어떻게 서버가 응답하는지 알아봅시다.
www.whatap.io
'IT > DEV Study' 카테고리의 다른 글
[통신/네트워크] CRC 정리. CRC-16 C#/VB 코드. 온라인 CRC 변환기 링크. (3) | 2023.02.07 |
---|---|
[VB] Dictionary Add & Remove Test (feat. LINQ 람다식) (0) | 2022.09.13 |
[Nomad Corders] NestJS로 API 만들기 #3 정리 (358) | 2022.07.26 |
[Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 (413) | 2022.07.25 |
[Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3 정리 (409) | 2022.07.25 |