NestJS + Jest 테스트에서 describe, beforeEach, spyOn을 활용하여 의존성을 주입하고 Mock 데이터를 설정하는 방법
🔍 개요
NestJS에서 Jest를 사용해 테스트 코드를 작성할 때,
- describe()를 사용해 테스트를 계층적으로 나누고,
- beforeEach()에서 공통 의존성을 주입하며,
- jest.spyOn()을 활용해 비즈니스 로직 내부의 특정 메서드를 Mocking하여 원하는 데이터를 설정할 수 있습니다.
이 글에서는 NestJS 서비스의 비즈니스 로직을 단위 테스트하는 과정에서 의존성을 주입하고, Mock 데이터를 설정하는 올바른 방법을 설명합니다.
1️⃣ Jest에서 의존성 주입과 Mock 데이터 설정이 필요한 이유
NestJS에서는 의존성 주입(Dependency Injection, DI)을 사용하여 서비스 간의 의존성을 관리합니다.
하지만 Jest에서 단위 테스트를 작성할 때, 실제 서비스 대신 Mock 데이터를 주입해서 테스트합니다.
이유는 다음과 같습니다:
- 독립적인 테스트를 수행하기 위해
- 실제 데이터베이스나 API 호출 없이 테스트를 실행해야 합니다.
- 테스트 실행 속도를 높이기 위해
- 실제 API나 DB를 호출하면 테스트가 느려지고, 불필요한 비용이 발생합니다.
- 예측 가능한 테스트를 위해
- jest.spyOn()을 사용하여 특정 메소드의 반환값을 제어할 수 있습니다.
2️⃣ NestJS에서 Jest 테스트 환경 설정
📌 1. 기본적인 테스트 코드 구조
다음은 NestJS 서비스에서 Jest를 사용한 기본적인 테스트 코드 구조입니다.
샘플 코드) Android 인앱결제 신호를 받는 비지니스 로직 테스트 코드
import { Test, TestingModule } from '@nestjs/testing';
import { AndroidRtdnCommandHandler } from './android-rtdn-command.handler';
import { IAPValidatorProvider } from '../providers/iap-validator.provider';
import { SubscriptionRepository } from '../subscription.repository';
import { AppConfigService } from 'src/config';
describe('AndroidRtdnCommandHandler', () => {
let handler: AndroidRtdnCommandHandler;
let iapValidatorProvider: jest.Mocked<IAPValidatorProvider>;
let subscriptionRepository: jest.Mocked<SubscriptionRepository>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AndroidRtdnCommandHandler,
{
provide: IAPValidatorProvider,
useValue: { validateGooglePurchase: jest.fn() }, // Mock 설정
},
{
provide: SubscriptionRepository,
useValue: { findByLatestOrderId: jest.fn() },
},
],
}).compile();
handler = module.get<AndroidRtdnCommandHandler>(AndroidRtdnCommandHandler);
iapValidatorProvider = module.get<IAPValidatorProvider>(IAPValidatorProvider);
subscriptionRepository = module.get<SubscriptionRepository>(SubscriptionRepository);
});
describe('processGooglePlayMessage', () => {
let validateGooglePurchaseSpy: jest.SpyInstance;
let findByLatestOrderIdSpy: jest.SpyInstance;
beforeEach(() => {
validateGooglePurchaseSpy = jest
.spyOn(iapValidatorProvider, 'validateGooglePurchase')
.mockResolvedValue({
latestOrderId: "test-order-id",
subscriptionState: 2,
testPurchase: false,
});
findByLatestOrderIdSpy = jest
.spyOn(subscriptionRepository, 'findByLatestOrderId')
.mockResolvedValue({
id: "test-id",
orderId: "test-order-id",
state: "active",
} as unknown as Subscription);
});
it('should call handleSubscriptionValidate for subscriptionNotification', async () => {
const message = {
subscriptionNotification: {
notificationType: 1,
purchaseToken: "test-token",
subscriptionId: "1month_subscription",
}
};
await handler.processGooglePlayMessage(message);
expect(validateGooglePurchaseSpy).toHaveBeenCalled();
expect(findByLatestOrderIdSpy).toHaveBeenCalled();
});
});
});
3️⃣ Jest에서 의존성을 주입하는 방법
📌 Test.createTestingModule을 사용해 의존성 주입
Test.createTestingModule()을 사용하면 NestJS의 providers를 Mock 객체로 대체하여 테스트 환경을 구성할 수 있습니다.
예제: beforeEach에서 의존성 주입
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AndroidRtdnCommandHandler,
{
provide: IAPValidatorProvider,
useValue: { validateGooglePurchase: jest.fn() }, // Mock 설정
},
{
provide: SubscriptionRepository,
useValue: { findByLatestOrderId: jest.fn() },
},
],
}).compile();
handler = module.get<AndroidRtdnCommandHandler>(AndroidRtdnCommandHandler);
iapValidatorProvider = module.get<IAPValidatorProvider>(IAPValidatorProvider);
subscriptionRepository = module.get<SubscriptionRepository>(SubscriptionRepository);
});
✅ 이렇게 설정하면 실제 서비스 대신 Mock 객체가 주입되므로, 테스트가 독립적으로 실행될 수 있습니다.
4️⃣ jest.spyOn()을 사용하여 특정 메소드 Mocking
📌 jest.spyOn()을 사용해 특정 함수의 반환값을 변경
단위 테스트에서는 비즈니스 로직이 내부에서 실행하는 특정 메소드를 Mocking할 필요가 있습니다.
예제: 특정 함수의 반환값을 변경
const validateGooglePurchaseSpy = jest
.spyOn(iapValidatorProvider, 'validateGooglePurchase')
.mockResolvedValue({
latestOrderId: "test-order-id",
subscriptionState: 2,
testPurchase: false,
});
const findByLatestOrderIdSpy = jest
.spyOn(subscriptionRepository, 'findByLatestOrderId')
.mockResolvedValue({
id: "test-id",
orderId: "test-order-id",
state: "active",
} as unknown as Subscription);
✅ 이렇게 하면 테스트에서 특정 함수의 반환값을 원하는 값으로 설정할 수 있습니다.
5️⃣ jest.spyOn()을 사용해 함수 호출 여부 검증
테스트 실행 후 특정 함수가 정상적으로 실행되었는지 확인해야 합니다.
📌 expect(...).toHaveBeenCalled()을 사용
it('should call handleSubscriptionValidate for subscriptionNotification', async () => {
const message = {
subscriptionNotification: {
notificationType: 1,
purchaseToken: "test-token",
subscriptionId: "1month_subscription",
}
};
await handler.processGooglePlayMessage(message);
expect(validateGooglePurchaseSpy).toHaveBeenCalled(); // ✅ 함수가 호출되었는지 확인
expect(findByLatestOrderIdSpy).toHaveBeenCalled();
});
✅ 이제 이 테스트는 특정 메소드가 호출되었는지 검증할 수 있습니다.
🚀 결론
✔ Jest에서 의존성을 주입하고 Mock 데이터를 설정하는 Best Practice
- Test.createTestingModule()을 사용해 Mock 서비스를 주입
- beforeEach()에서 공통적으로 필요한 Mock 설정을 적용
- jest.spyOn()을 사용해 내부 메소드의 반환값을 설정
- expect(...).toHaveBeenCalled()을 사용해 함수 호출 여부를 검증
- 중복된 Mock 설정을 beforeEach()에서 한 번만 설정하여 유지보수를 쉽게 만듦
💡 이렇게 하면?
- ✅ 독립적인 단위 테스트 가능
- ✅ 외부 API나 DB 호출 없이 빠른 테스트 실행
- ✅ 비즈니스 로직이 의도한 대로 동작하는지 검증 가능
'NestJS' 카테고리의 다른 글
[Nest.js] PIPE (0) | 2023.08.27 |
---|---|
[Nest.js] TypeORM 이용한 CRUD (0) | 2023.08.22 |
[Nest.js] 기본 파일 구조 및 요청 흐름 (2) | 2023.08.20 |
[Nest.js] Quick Start! (0) | 2023.08.15 |