상세 컨텐츠

본문 제목

[nestjs] spec과 unit testing

Web/NestJS

by 클리엘 클리엘 2021. 3. 17. 10:37

본문

728x90

nestjs구조를 보면 거의 모든 controller.ts와 service ts파일에 spec.ts파일이 같이 붙어 있는 것을 볼 수 있습니다. spec.ts파일은 테스트를 위한 파일입니다.

import { Test, TestingModule } from '@nestjs/testing';
import { SchoolController } from './school.controller';

describe('SchoolController', () => {
  let controller: SchoolController;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [SchoolController],
    }).compile();

    controller = module.get<SchoolController>(SchoolController);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});

package.json을 보면 테스트가능한 설정을 볼 수 있으며

    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"

각 명령을 통해서 테스트를 수행할 수 있습니다. 보시는 바와 같이 테스트는 jest가 사용되며 nestjs를 통해 이미 jest를 사용하기 위한 설정과 설치가 이루어져 있는 상태입니다.

 

우선 터미널에서 test:cov를 통해 전체적으로 얼마나 테스트가 진행되었는지를 확인해 보겠습니다. jest는 프로젝트의 spec.ts파일을 찾아 얼마나 테스트되었는가를 표시할 것입니다.

 

이 말은 만약 테스트하고자 하는 특정 페이지가 있다면 해당 페이지의 spec.ts파일을 생성해 두면 된다는 뜻이기도 합니다. 반대로 테스트가 필요하지 않다면 spec.ts는 제거해도 됩니다.

 

다시 아래와 같이 테스트를 진행시키고

npm run test:watch

a를 눌러 전체테스트를 시도합니다.

 

예제에서는 school.controller.spec.ts 하나만 남겨놓고 진행한 것인데 총 1개의 파일 중 1개의 파일 테스트가 실패했음을 알려주고 있습니다.

 

Unit 테스트는 school.controller.spect.ts에 있는 내용처럼 it을 통해 정의된 부분을 테스트하는 것입니다. 기본적으로 포함된 it 외에 필요한 함수를 추가해 테스트하는 방식으로 진행합니다. 테스트를 진행하기 전 위에서 발생한 에러부터 해결하고 진행하겠습니다.

 

에러를 보면 'SchoolController › should be defined' 라고 표시하는데 말 그대로 SchoolController가 정의되어 있지 않다고 합니다. 이는 SchoolController에서 추가한 생성자 때문인데 SchoolController클래스는 생성자를 통해 SchoolService를 받고 있고 이를 테스트 모듈을 생성할 때 SchoolService가 제대로 전달되지 않아서 생기는 문제입니다. 따라서 providers를 통해 SchoolService를 전달해야 합니다. school.controller.spect.ts를 다음과 같이 수정합니다.

beforeEach(async () => {
  const module: TestingModule = await Test.createTestingModule({
    providers: [SchoolService],
    controllers: [SchoolController],
  }).compile();

  controller = module.get<SchoolController>(SchoolController);
});

beforeEach는 테스트가 시작되기전 수행되는 부분이며 필요하다면 afterAll이나 afterEach같이 테스트 후에 작동할 함수를 별도로 만들수도 있습니다.

 

위에서 테스트를 시작할때 test:warch모드로 실행했기 때문에 파일이 수정되면 즉시 테스트가 진행될 것입니다.

 

정상적으로 처리되었으니 이제 테스트할 함수를 추가해 테스트를 진행해 보도록 하겠습니다.

 

school.controller.spec.ts를 보면 첫 번째 describe가 'SchoolController'라고 되어 있는데 이것으로 school.controller.spec.ts 파일이 SchoolController부분을 테스트하기  위한 스크립트 파일임을 알 수 있습니다. 또한 첫 번째 if에서는 'expect(controller).toBeDefined();'에 의해 controller가 정의되어 있는지의 여부를 테스트하는 함수인데 위에서 오류가 발생한 부분은 바로 이 함수에서 발생시킨 에러입니다. 참고로 it에 있는 'should be defined'는 테스트 명칭으로 테스트를 구분할 수 있는 임의의 명칭을 지정하면 됩니다.

 

이제 SchoolController애서 구현한 'getStudents()'함수를 테스트하기 위해 새로운 테스트함수를 아래와 같이 추가합니다.

it('getStudents() 의 실행 결과 테스트', () => {
  const result = controller.getStudents();
  expect(result).toBeInstanceOf(Array);
});

 

 

혹은 다음과 같이 새로은 describe를 만들어 함수를 추가할 때 테스트 별로 그룹을 나눌 수도 있습니다.

 

SchoolController 함수는 spec.ts파일에서 Controller로 생성하였으니 Controller를 통해 함수로 접근하면 됩니다. 예제는 getStudents() 함수를 실행하고 그 결과가가 Array의 인스턴스와 일치하는지를 확인하고 있습니다.

 

테스트에 실패했습니다. 테스트에서는 방금지정한 대로 Array가 나와야 하는데 Promise가 나왔으며 예상과 다른 결과가 나왔으니 오류를 내는 것입니다.

 

school.controller.ts에서 getStudents()함수는 비동기로 호출하였습니다. 때문에 테스트에서는 이를 promise로 판단한 것인데 그렇다면 school.controller.ts에서 비동기를 빼거나 혹은 school.controller.spec.ts에서 테스트 함수를 비동기로 하여 처리하는 방법이 있을 것 같습니다. 예제는 school.controller.spec.ts를 수정하여 비동기로 getStudents() 함수를 호출하도록 하겠습니다.

 

 

it('getStudents() 의 실행 결과 테스트', async () => {
  const result = await controller.getStudents();
  expect(result).toBeInstanceOf(Array);
});

getStudents()의 테스트결과가 4ms 만에 테스트가 완료되었음을 알 수 있습니다.

 

두 번째 getStudent() 함수는 단 하나의 student객체만 반환하므로 테스트를 다음과 같이 작성합니다.

  it('getStudent() 의 실행 결과 테스트', async () => {
    controller.setStudent({
      name: '홍길동',
      group: 'A',
      age: 20
    });

    const result = await controller.getStudent(1);

    expect(result).toBeDefined();
  });

새로운 student를 생성하고 getStudent(1);로 함수를 실행하면 생성한 student를 반환할 것이므로 결과는 정의되었을 것이라고 명시하고 있습니다.

 

expect하나를 더 추가해 결과로 반환된 id를 값을 확인하고 있습니다. 새롭게 추가한 student의 id는 1일이 되어야 합니다.

it('getStudent() 의 실행 결과 테스트', async () => {
  controller.setStudent({
    name: '홍길동',
    group: 'A',
    age: 20
  });

  const result = await controller.getStudent(1);

  expect(result).toBeDefined();
  expect(result.id).toEqual(1);
});

student.controller.ts에서 getStudent() 함수를 따라가 보면 student.service.ts에서는 아래와 같이 구현되어 있음을 알 수 있습니다.

getStudent(id: number) {
    const std = this.students.find(x => x.id === id);

    if (!std) {
        throw new NotFoundException('student 정보 없음');
    }

    return std;
}

지정한 student가 존재하지 않으면 NotFoundException예외를 발생시키는 것입니다. 이것도 잘 작동하는지 확인해 보도록 하겠습니다.

describe('controller 요청 테스트 모음', () => {
  it('getStudents() 의 실행 결과 테스트', async () => {
    const result = await controller.getStudents();
    expect(result).toBeInstanceOf(Array);
  });

  it('getStudent() 의 실행 결과 테스트', async () => {
    controller.setStudent({
      name: '홍길동',
      group: 'A',
      age: 20
    });

    const result = await controller.getStudent(1);

    expect(result).toBeDefined();
    expect(result.id).toEqual(1);
  });

  it('getStudent()의 오류 발생 테스트', async () => {
    try {
      const result = await controller.getStudent(100);
    }
    catch(err) {
      expect(err).toBeInstanceOf(NotFoundException);
    }
  });
});

아래 화면에서는 school.controller.ts의 테스트가 아직 80% 정도밖에 이루어지지 않았다고 표시하고 있습니다.

 

npm run test:cov

school.controller.ts의 모든 함수에 대해 테스트가 이루어 져야만 100%가 될 수 있을 것 같습니다. 하지만 귀찮아서 패스...

728x90

'Web > NestJS' 카테고리의 다른 글

[nestjs] TypeORM 기존 테이블에서 entity 생성하기  (0) 2021.03.17
[nestjs] spec과 unit testing  (0) 2021.03.17
[nestjs] module과 의존성주입(dependency injection)  (0) 2021.03.16
[nestjs] DTO (Data Transfer Object)  (0) 2021.03.16
[nestjs] validation  (0) 2021.03.16
[nestjs] Service  (0) 2021.03.16

태그

관련글 더보기

댓글 영역