
테스트 코드를 사용하는 이유는 여러가지가 있다.
우선, 새로운 Nest.js 프로젝트를 생성한다.
nest new my-nest-project
cd my-nest-project
Nest.js 프로젝트를 생성하면 기본적으로 src 폴더 안에 app.controller.spec.ts 파일이 생성된다. 이 파일은 기본적으로 테스트 코드를 포함하고 있다.
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
테스트를 실행하려면 다음 명령어를 사용한다.
npm run test
새로운 서비스를 추가하고, 그 서비스의 테스트 코드를 작성 한다면
nest g service my
import { Injectable } from '@nestjs/common';
@Injectable()
export class MyService {
getHello(): string {
return 'Hello from MyService!';
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { MyService } from './my.service';
describe('MyService', () => {
let service: MyService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MyService],
}).compile();
service = module.get<MyService>(MyService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return "Hello from MyService!"', () => {
expect(service.getHello()).toBe('Hello from MyService!');
});
});
컨트롤러를 테스트하는 코드도 비슷한 방식으로 작성할 수 있다.
nest g controller my
my.controller.ts
import { Controller, Get } from '@nestjs/common';
import { MyService } from './my.service';
@Controller('my')
export class MyController {
constructor(private readonly myService: MyService) {}
@Get()
getHello(): string {
return this.myService.getHello();
}
}
my.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { MyController } from './my.controller';
import { MyService } from './my.service';
describe('MyController', () => {
let controller: MyController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [MyController],
providers: [MyService],
}).compile();
controller = module.get<MyController>(MyController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('should return "Hello from MyService!"', () => {
expect(controller.getHello()).toBe('Hello from MyService!');
});
});
Nest.js에서 테스트 코드를 작성하면 코드의 안정성을 높이고, 버그를 사전에 방지하며, 개발 속도를 높일 수 있다. Jest를 사용하여 테스트 코드를 쉽게 작성할 수 있으며, 테스트 코드는 서비스와 컨트롤러를 포함한 다양항 부분에 대해 작성할 수 있다.
MyController의 동작 테스트 코드에 대해서 설명을 하자면
describedescribe는 테스트 스위트(test suite)를 정의하는 함수이다. 하나의 테스트 스위트는 관련된 여러 개의 테스트 케이스를 그룹화 한다. 첫 번째 인자는 테스트 스위트의 이름(설명)이고, 두 번째 인자는 테스트 케이스들을 포함하는 함수 이다.
describe('MyController', () => {
// 테스트 케이스들이 여기에 포함됩니다.
});
beforeEach / afterEachbeforeEach는 각 테스트 케이스가 실행되기 전에 공통적으로 실행되어야 하는 설정 작업을 정의 한다. 이 예제에서는 beforeEach를 사용하여 TestingModule을 컴파일하고, MyController의 인스턴스를 가져온다.
beforeEach(async () => {
// 각 테스트 케이스가 실행되기 전에 실행
const module: TestingModule = await Test.createTestingModule({
controllers: [MyController],
providers: [MyService],
}).compile();
controller = module.get<MyController>(MyController);
});
afterEach(() => {
// 각 테스트 케이스가 실행된 후에 실행
});
it / testit / test 는 개별 테스트 케이스를 정의하는 함수이다. 첫 번째 인자는 테스트 케이스의 설명이고, 두 번째 인자는 테스트가 수행될 함수이다. it 블록 안에는 테스트 대상 코드와 그 코드의 결과를 검증하는 코드가 들어간다.
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('should return "Hello from MyService!"', () => {
expect(controller.getHello()).toBe('Hello from MyService!');
});
beforeAll / afterAll테스트 스위트가 실행되기 전과 후에 각각 한 번씩 실행되는 함수
beforeAll(() => {
// 전체 테스트 스위트가 실행되기 전에 한 번 실행
});
afterAll(() => {
// 전체 테스트 스위트가 실행된 후에 한 번 실행
});
expectexpect는 Jest의 단언(assertion) 함수이다. 테스트 대상의 값을 받아서, 그 값이 기대하는 조건을 만족하는지 확인한다.
expect(controller).toBeDefined();
toBe: 실제 값이 기대한 값과 정확히 일치하는지 확인.
toEqual: 객체나 배열과 같은 값의 구조가 일치하는지 확인.
toBeTruthy: 값이 참인지 확인.
toBeFalsy: 값이 거짓인지 확인.
toContain: 배열이나 문자열에 특정 값이 포함되어 있는지 확인.
toHaveLength: 배열이나 문자열의 길이가 특정 값인지 확인.
toHaveProperty: 객체가 특정 프로퍼티를 가지고 있는지 확인.
toMatch: 문자열이 특정 정규식과 일치하는지 확인.
toThrow: 함수가 예외를 던지는지 확인.
expect(value).toEqual(expectedValue);
expect(array).toContain(element);
expect(string).toMatch(/pattern/);
expect(fn).toThrow();
tobetobe는 Jest의 매처(matcher) 함수 중 하나로, expect와 함께 사용되어 실제 값이 기대하는 값과 정확히 일치하는지 확인한다.
expect(controller.getHello()).toBe('Hello from MyService!');
Jest에서 mock 함수를 사용하여 함수 호출과 결과를 시뮬레이션할 수 있다.
jest.fn: 기본 mock 함수 생성.jest.spyOn: 기존 객체 매서드를 감시하고 모킹.const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
const obj = { method: jest.fn() };
obj.method();
expect(obj.method).toHaveBeenCalled();
const obj = { method: () => 'real implementation' };
const spy = jest.spyOn(obj, 'method').mockImplementation(() => 'mock implementation');
expect(obj.method()).toBe('mock implementation');
spy.mockRestore();
테스트 코드를 모든 상황에서 작성하는 것이 이상적이지만, 현실적으로 프로젝트의 규모와 성격, 개발 리소스 등에 따라 우선순위를 정하고 필요한 부분부터 작성하는 것이 좋다.
모든 코드에 대해 테스트 코드를 작성하는 것이 이상적이지만, 현실적으로 우선순위를 정해 중요한 부분부터 작성하는 것이 효율적이다. 핵심 비즈니스 로직, 복잡한 로직, 자주 변경되는 코드, 외부 API 통신, 보안 관련 기능 등에는 우선적으로 테스트 코드를 작성하고, 프로젝트의 상황에 따라 점진적으로 테스트 커버리지를 확대해 나가는 것이 좋다.