NestJS | API 만들기(4) - Testing

Sua·2021년 3월 27일
0

NestJS

목록 보기
4/9
post-thumbnail
post-custom-banner

노마드코더강의를 들으면서 NestJS로 영화 API를 만들고 있다.

package.json 파일을 보면 테스팅과 관련된 스크립트가 5가지 정도 있다.

jest는 자바스크립트를 아주 쉽게 테스팅하는 npm 패키지이다. NestJS가 세팅을 다 해뒀다.

지금까지 생성한 파일들을 보면 파일명 뒤에 .spec.ts가 붙은 파일들이 있다. 얘네들은 테스트를 포함한 파일이다. 만약 movie.controller.ts를 테스트하고 싶다면 movie.controller.spec.ts 파일이 있어야 한다.
NestJS에서는 jset가 spec.ts파일을 찾아볼 수 있도록 설정되어 있다.

cov mode는 코드가 얼마나 테스팅 됐는지 알려준다.
npm run test:cov 커맨드를 치면 spec.ts 파일을 찾아서 몇 줄이 테스팅됐는지 이렇게 나타내준다.

이제 watch mode에서 테스팅을 돌려보자.
npm run test:watch
모든 테스트 파일들을 찾아내서 거기서 무슨 일이 일어나는지 보는 것이다.

모든 테스트를 실행하려면 a를 누른다.

movies.service.spec.ts가 테스트를 실행하는 것을 볼 수 있다.

이제 몇 가지 유닛 테스트를 추가해볼 것이다. 유닛 테스트란 모든 function을 따로 테스트하는 것이다.

반면, end-to-end(e2e)는 모든 시스템을 테스팅하는 것이다. 예를 들어 이 페이지로 가면 특정페이지가 나와야 하는 경우에 쓰인다. 사용자 관점에서 테스팅하는 것이다. 감사하게도 NestJS에는 package.json e2e라는 스크립트가 있다.

먼저 유닛 테스팅을 사용해서 MovieService를 테스팅할 것이다.

Unit Test

NestJS가 만들어 놓은 movies.service.spec.ts 파일을 보자.
describe는 테스트를 묘사하는 것이고, beforeEach는 테스트를 하기 전에 실행되는 부분이다.
it 이 부분이 실제로 테스트 하는 부분이다.

// movies.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();
  });
});

실제로 간단한 테스트를 작성해보자.

  it('should be 4', () => {
    // 조건, 2+2가 4와 같기를(toEqual) 기대(expect)하고 있다. 
    expect(2+2).toEqual(4);
  });

test watch를 실행시켰기 때문에 새로운 테스트를 만들어낼 때마다 테스트가 실행된다. should be 4가 pass된 것을 확인할 수 있다.

만약 4를 5로 바꾸면 1개 실패, 1개 성공이라고 나온다.

어떻게 돌아가는지 알았으니 테스트한 코드는 삭제한다.

이제 본격적으로 MovieSerive의 유닛 테스트를 작성해보자.

getAll()

먼저 getAll() 함수를 테스트해보자.
describe 안에 새로운 describe를 추가하고, 그 안에 테스트 코드를 만든다.
describe의 첫 번째 인자로는 함수명을 적어주는 게 좋다. 다른 걸로 해도 상관은 없다.

getAll()이 실행할 때마가 Movie 배열을 반환하는 것을 테스트해보자.
'should return an array'라고 하고, getAll()을 호출하고 result가 배열을 리턴하는지 테스트하는 로직을 짠다.

(const result = service.getAll()에서 NestJS 장점 덕분에 service로 MovieService에 접근할 수 있다.)

describe('MoviesService', () => {
  let service: MoviesService;

  ...(생략)

  describe('getAll', () => {
 
    it('should return an array', () => {
      const result = service.getAll();
      expect(result).toBeInstanceOf(Array);
    });
  });

테스트 코드가 잘 작성되었다면 test watch에 'should return an array'가 잘 출력되는 것을 확인할 수 있다.

getOne()

그리고 getOne() 함수를 테스트해보자.
getOne()을 테스트할 때 Movie가 create가 되어 있지 않다면 문제가 될 수 있다. 먼저 movie를 생성한다.

그리고, 404 에러 처리 테스트 코드까지 작성해줘야 한다.

  describe('getOne', () => {
    it('should return a movie', () => {
      service.create({
        title: 'Test Movie',
        year: 2020,
        genres: ['test']
      });
      const movie = service.getOne(1);
      expect(movie).toBeDefined();
      expect(movie.id).toEqual(1); // 필수X
    });
    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.') // 필수X
      }
    });
  });

test watch로 확인

deleteOne()

  // 로직 : 잘 작동해서 movie 하나를 지우는 거고 다른 하나는 지우려는 movie를 못 찾는 것
  describe('deleteOne', () => {
    it('deletes a movie', () => {
      // movie 생성, movie 목록 확인, movie를 삭제하고, movie 목록 확인
      service.create({
        title: 'Test Movie',
        year: 2020,
        genres: ['test']
      });
      const allMovies = service.getAll();
      service.deleteOne(1);
      const afterDelete = service.getAll();
      // 한 개가 삭제되었으므로 movie 배열의 길이가 -1이 되어야 함
      expect(afterDelete.length).toEqual(allMovies.length - 1);
    });
    it('should return a 404', () => {
      try {
        service.deleteOne(999);
      } catch (e) {
        expect(e).toBeInstanceOf(NotFoundException);
      }
    });
  });

create()

  describe('create', () => {
    it('should create a movie', () => {
      const beforeCreate = service.getAll().length;
      service.create({
        title: 'Test Movie',
        year: 2020,
        genres: ['test']
      });
      const afterCreate = service.getAll().length;
      expect(afterCreate).toBeGreaterThan(beforeCreate);
      // 마지막으로 생성된 movie의 title이 일치하는지도 테스트할 수 있음
    });
  });

update()

  describe('update', () => {
    it('should update a movie', () => {
      service.create({
        title: 'Test Movie',
        year: 2020,
        genres: ['test']
      }); // 테스트 케이스마다 생성하고 싶지 않다면 beforeEach에 생성하면 된다. 
      service.update(1, { title: 'Updated Test' });
      const movie = service.getOne(1);
      expect(movie.title).toEqual('Updated Test');
    });
    it('should throw a NotFoundException', () => {
      try {
        service.update(999, {});
      } catch (e) {
        expect(e).toBeInstanceOf(NotFoundException);
      }
    });
  });

test watch로 확인

test cov로 확인하면 movie.service.ts 파일의 coverage가 100%, 완벽히 테스팅된 것을 알 수 있다.

+) beforeEach, afterEach, beforeAll, afterAll 등 많은 Hook들을 활용해보자.

e2e Test

profile
Leave your comfort zone
post-custom-banner

0개의 댓글