NestJS에서는 기본적으로 jest를 활용한 테스트 기능을 제공합니다. 오늘은 jest를 활용해 유닛 테스트하는 방법에 대해 알아보겠습니다.
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
기본 예제
# auth.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
위 코드는 nest 명령어를 이용해 service 코드를 생성했을 때 service 코드와 같이 생성되는 service 테스트 코드입니다. (저의 경우 auth라는 이름으로 서비스를 생성했습니다) 위 코드를 이해하는 과정을 통해 기본적인 jest 코드 구조에 대해 알아보겠습니다.
describe('AuthService', () => {
let service: AuthService;
먼저 describe 부분은 테스트 그룹을 설정하는 부분이라고 이해하면 됩니다. 해당 테스트는 AuthService에 대한 유닛 테스트를 진행할 것임을 확인할 수 있습니다.
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
각 테스트는 it 명령어를 통해 구현됩니다. 이때 beforeEach는 각 it 테스트가 진행되기 전에 적용되어야 하는 설정이라고 이해하면 됩니다. 위 코드에서는 Test.createTestingModule 명령어를 통해 테스트 모듈을 생성하고 AuthService 의존성을 주입하여 컴파일했습니다. 그리고 이를 service라는 값에 할당했습니다.
it('should be defined', () => {
expect(service).toBeDefined();
});
it 부분은 실질적인 테스트가 이뤄지는 부분입니다. 위 코드는 beforeEach에서 설정했던 service값이 잘 정의되었는지 테스트하는 것입니다.
테스트 코드 작성하기
저는 새로운 프로젝트를 만들었고, 여기서 TDD(Test-Driven Development) 방식 개발을 연습하려 합니다. TDD 개발을 단순하게 설명하면 테스트 케이스부터 완성하고, 이후에 해당 테스트 케이스를 모두 통과할 수 있도록 구현을 차근차근하는 개발 방식을 의미합니다.
1. 기본 예제 생성
먼저 'nest g resource auth' 명령어를 통해 auth 폴더로 기본적인 rest api 예제 코드를 만들었습니다. 이후 auth 서비스 레이어 코드에서 회원가입 성공에 대한 테스트 코드를 하나 작성하려 합니다.
2. auth.service.spec.ts 작성
다음은 제가 작성해 본 테스트 코드입니다. 회원가입을 시도했을 때 유저 서비스에서 유저를 데이터베이스에 저장하는 로직에 대한 테스트, 회원가입 이후 바로 JWT 토큰을 발급해 주는 로직에 대한 테스트가 필요하다 생각해 아래와 같이 작성했습니다.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from 'src/user/user.service';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let authService: AuthService;
let userService: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{
provide: UserService,
useValue: {
create: jest.fn(),
},
},
],
}).compile();
authService = module.get<AuthService>(AuthService);
userService = module.get<UserService>(UserService);
});
describe('회원가입', () => {
it('회원가입 성공', async () => {
// given
const signUpData = {
email: 'test@example.com',
provider: 'google',
token: '123456789',
};
const mockJwt = {
accessToken: 'accessTokenString',
refreshToken: 'refreshTokenString',
};
// when
const result = await authService.signUp(signUpData);
// then
expect(userService.create).toHaveBeenCalledWith(signUpData);
expect(result).toEqual(mockJwt);
});
});
});
given when then 패턴을 활용해 테스트 코드를 작성했습니다. 회원가입에 필요한 데이터가 들어와서 signUp 메서드가 실행된 경우 userService의 create 메서드가 호출되어야 하고, 결과적으로 반환하는 값은 JWT 토큰이 반환되어야 함을 테스트하기 위해 위와 같이 작성해보았습니다.
🤔 문제점
이런 테스트 코드를 작성하는 과정에서 여러 문제점(어려움)이 발생했습니다. 제가 느낀 문제점들은 다음과 같습니다.
- 아직 구현되지도 않은 메서드를 어떻게 호출하지?
기존에 저는 메서드를 구현해야 할 때 바로바로 이름과 로직을 생각해 내면서 코드를 작성했습니다. 어떻게 보면 체계 없이, 계획 없이 코드를 작성했다고 볼 수 있습니다. 하지만 이런 방식이 빠른 개발에 용이했기 때문에 저는 이런 방식을 선호했습니다.
이런 상황에서 테스트 코드부터 작성하려고 보니, 어떤 부분을 체크해야 할지 가늠이 되지 않았습니다. - 어느 수준까지 세세하게 테스트를 진행해야 한가?
하나의 서비스 코드 안에서 다른 서비스를 의존하는 경우도 많습니다. 이럴 경우 의존하는 서비스에서 에러가 발생할 수도 있는데, 이런 의존성들도 모두 테스트를 진행해야 하는지가 애매했습니다.
💡내가 생각하는 효율적인 테스트
저는 개인적으로 위와 같은 문제점을 겪으면서 TDD에 대한 의심을 품게 되었습니다. TDD가 오히려 생산성과 효율성을 떨어트리는 개발 방법론은 아닌가라는 생각이 들었습니다. 테스트 코드를 개발 전에 작성하기 위해 고민하는 시간이 너무나 오래 걸렸기 때문에, 오히려 테스트 코드는 개발 전에 작성하는 것이 아니라, 개발이 어느 정도 완성된 후에 작성하는 것이 더 빠른 테스트 코드 작성에 도움이 될 것이라 생각했습니다. 또한 테스트 코드를 작성하는 과정에 그 결과가 너무나도 자명한 경우도 상당히 많이 발생한다고 생각했습니다. 그렇기 때문에 모든 상황을 테스트하기 위해 엄청난 수의 테스트 케이스를 작성하는 수고를 하지 않고, 헷갈릴 수 있거나 내가 고려하지 못해서 발생했던 실제 에러에 대한 테스트 케이스만 작성하는 것이 훨씬 효율적일 수 있다고 생각했습니다.
사실 저는 테스트 코드를 작성해 본 경험 자체가 굉장히 적기 때문에 섣불리 테스트 코드에 대한 비효율성을 확신할 수는 없습니다. 그렇기에 일단 당분간은 다양한 테스트 코드 작성 경험을 쌓고 보다 더 효율적인 테스트 방법에 대해 고민해보려 합니다.
'백엔드 > NestJS' 카테고리의 다른 글
[NestJS] 동적 라우팅 오류 없애기( route paramter, path variable ) (0) | 2024.11.28 |
---|---|
[NestJS] 컨트롤러에서 브레이크 포인트 안 걸릴 때 체크해야할 것들! ( 동적 경로, 정적 경로, route parameter ) (0) | 2024.11.25 |
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 4. Kafka (0) | 2024.11.04 |
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 3. MQTT (0) | 2024.11.04 |
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 2. TCP (0) | 2024.11.03 |