[Nest.js] 2. Nest.js로 REST API 만들기
이전 글을 읽고 Nest.js 설정을 하는 게 좋음. [Nest.js] 1. Nest.js 프로젝트 구조 파악하기노마드 코더님의 nest.js 기초 강의를 토대로 필기한 것입니다.nest.js란node.js의 웹 프레임워크로, express위에서
sksmsfbrjs51.tistory.com
Introduction of Testing in Nest
package.json 파일에는 테스팅과 관련된 스크립트가 5개가 있다. 테스트 시에는 jest 사용 (js를 쉽게 테스팅하는 npm 패키지)
- test
- test:watch
- npm run test:watch (모든 테스트파일을 관찰해서 무슨 일이 일어나는 지 관찰)
- test:cov
- coverage mode
- test:debug
- test:e2e
NestJS에서 파일을 생성하면 .spec.ts라는 파일이 생성된다. 이는 테스트를 위한 파일이므로 jest를 통해 .spec.ts를 찾아 볼 수 있다.
테스팅에는 두 가지가 있다.
- unit test : 모든 function을 따로 테스트하는 것.
- ex> service의 특정 함수를 테스트하고 싶을 때
- end-to-end(e2e) : 모든 시스템 테스트
- (ex> 이 페이지로 가면 특정 페이지가 나와야 하는 경우 - 사용자 관점)
Unit Test
남겨놨던 movie.service.spec.ts에 직접 유닛 테스트를 작성해보자
- movie.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { MoviesService } from './movies.service';
describe('MoviesService', () => {
let service: MoviesService;
// 테스트 하기 전 실행
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MoviesService],
}).compile();
service = module.get<MoviesService>(MoviesService);
});
// 정의 되어야 함.
it('should be defined', () => {
expect(service).toBeDefined();
});
});
- toEqual()
it('should be 4', () => {
expect(2 + 2).toEqual(4); // 2 + 2가 4와 같기를 기대한다.
});
Testing getAll and getOne
import { Test, TestingModule } from '@nestjs/testing';
import { MoviesService } from './movies.service';
import { NotFoundException } from '@nestjs/common';
describe('MoviesService', () => {
let service: MoviesService;
// 테스트 하기 전 실행
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MoviesService],
}).compile();
service = module.get<MoviesService>(MoviesService);
});
// 정의 되어야 함.
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('getAll()', () => {
it('should return an array.', () => {
const result = service.getAll();
expect(result).toBeInstanceOf(Array); // type이 array인지 체크
});
});
describe('getOne()', () => {
it('should return a movie.', () => {
service.create({
title: 'Test movie',
genres: ['Test'],
year: 2000,
});
const movie = service.getOne(1);
expect(movie).toBeDefined(); // 정의가 되었는가
expect(movie.id).toEqual(1); // 1과 같은가.
});
it('should throw 404 error', () => {
try {
service.getOne(999);
} catch (e) {
expect(e).toBeInstanceOf(NotFoundException);
expect(e.message).toEqual(`Movie with ID 999 Not Found`);
}
});
});
});
이후 터미널에서 npm run test:cov 으로 테스트
Testing delete and create
describe('deleteOne()', () => {
it('deletes a movie', () => {
service.create({
title: 'Test movie',
genres: ['Test'],
year: 2000,
});
const allMovies = service.getAll().length; // 1
service.deleteOne(1);
const afterDelete = service.getAll().length; // 0
expect(afterDelete).toBeLessThan(allMovies);
});
});
it('should return 404 error', () => {
try {
service.deleteOne(999);
} catch (e) {
expect(e).toBeInstanceOf(NotFoundException);
}
});
describe('create', () => {
it('should create a movie', () => {
const beforeCreate = service.getAll().length;
service.create({
title: 'Test movie',
genres: ['Test'],
year: 2000,
});
const afterCreate = service.getAll().length;
expect(afterCreate).toBeGreaterThan(beforeCreate);
});
});
Testing update
describe('update', () => {
it('should update a movie', () => {
service.create({
title: 'Test movie',
genres: ['Test'],
year: 2000,
});
service.update(1, { title: 'Updated Test' });
const movie = service.getOne(1);
expect(movie.title).toEqual('Updated Test');
});
it('should return a 404', () => {
try {
service.update(999, {});
} catch (e) {
expect(e).toBeInstanceOf(NotFoundException);
}
});
});
- beforeEach : 테스팅 시작 전 각 유닛테스트에 해주는 작업
- afterEach : 테스팅 후 각 유닛 테스트에 해주는 작업
- afterAll : 모든 테스팅 후 해주는 작업
- ex> DB clearing
spec.ts 파일들 → 전부 unit testing
Testing movies
e2e testing 진행 (애플리케이션 테스팅 - ex> 비밀번호를 입력 후 로그인 하는 과정을 테스트하고 싶을 때)
- 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();
});
// 서버에 reqeust를 보내면 200과 hello world를 받아야 한다.
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
즉, API, controller, service, pipe 등 전체적인 플로우를 테스팅할 수 있다.
- delete, post, get에 대한 e2e test
describe('/movies', () => {
it('/ (GET)', () => {
return request(app.getHttpServer()) // localhost:3000/ 생략하는 것.
.get('/movies')
.expect(200)
.expect([]);
});
it('POST', () => {
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
it('DELETE', () => {
return request(app.getHttpServer())
.delete('/movies')
.expect(404);
});
});
E2E Testing
e2e testing 진행 (애플리케이션 테스팅 - ex> 비밀번호를 입력 후 로그인 하는 과정을 테스트하고 싶을 때)
- 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();
});
// 서버에 reqeust를 보내면 200과 hello world를 받아야 한다.
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
즉, API, controller, service, pipe 등 전체적인 플로우를 테스팅할 수 있다.
- delete, post, get에 대한 e2e test
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();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Welcome to my Movie API');
});
**describe('/movies', () => {
it('/ (GET)', () => {
return request(app.getHttpServer()) // localhost:3000/ 생략하는 것.
.get('/movies')
.expect(200)
.expect([]);
});
it('POST', () => {
return request(app.getHttpServer())
.post('/movies')
.send({
title: 'Test',
year: 2000,
genres: ['test'],
})
.expect(201);
});
it('DELETE', () => {
return request(app.getHttpServer())
.delete('/movies')
.expect(404);
});
});**
});
Testing GET movies id
beforeEach 안의 createNestApplication 때문에 테스트를 할 때마다 계속 application을 생성해준다.
이러면 매 테스트마다 DB가 날라가므로 어떠한 더미 데이터를 남기고 싶으면 beforeAll로 로직을 수정하는 것이 좋다.
- app.e2e-spec.ts
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
그리고 todo 로직을 수정한다.
describe('/movies/:id', () => {
it('GET 200', () => {
return request(app.getHttpServer()).get('/movies/1').expect(200);
});
it.todo('DELETE');
it.todo('PATCH');
});
→ 위에서 post로 데이터를 만들었으니깐 200이 뜰 줄 알았는데 에러 발생!!
- movies.service.ts로 들어가서 getOne()에 넘긴 id의 타입을 출력해보자.
getOne(id: number): Movie {
console.log(typeof id);
...
}
→ 결과 : 실제 애플리케이션 실행 시 number로 나오지만 test 환경에서는 string으로 나온다.
즉, 테스트 서버에서도 똑같이 transform 을 적용해야 한다.
- app.e2e-spec.ts
...
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({
whitelist:true, // 아무 decorator 도 없는 어떤 property의 object를 거름
forbidNonWhitelisted: true, // 잘못된 property의 리퀘스트 자체를 막아버림
transform: true, // 실제 원하는 타입으로 변경해줌
}));
await app.init();
});
...
변경 후 정상적으로 작동한다. 다시 todo 부분을 수정해보자
describe('/movies/:id', () => {
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', () => {
return request(app.getHttpServer())
.patch('/movies/1')
.send({ title: 'Update test' })
.expect(200);
});
it('DELETE', () => {
return request(app.getHttpServer()).delete('/movies/1').expect(200);
});
});
- 잘못된 데이터를 가진 movie를 create 하는지 테스트
it('POST 201', () => {
return request(app.getHttpServer())
.post('/movies')
.send({
title: "Test",
year: 2000,
genres: ['test']
})
.expect(201); // 생성
});
it('POST 400', () => {
return request(app.getHttpServer())
.post('/movies')
.send({
title: "Test",
year: 2000,
genres: ['test'],
other: 'thing'
})
.expect(400); // 생성
});
'Back-end > Nest.js' 카테고리의 다른 글
[Nest.js] 2. Nest.js로 REST API 만들기 (0) | 2024.07.02 |
---|---|
[Nest.js] 1. Nest.js 프로젝트 구조 파악하기 (0) | 2024.07.02 |