NestJS는 테스트를 언어 수준의 기능처럼 다룬다. @nestjs/testing 패키지가 제공하는 TestingModule을 사용하면, 실제 애플리케이션과 동일한 DI 컨테이너를 테스트 환경에서 그대로 재현할 수 있다. 덕분에 Mock을 주입하더라도 프로덕션 코드 구조를 바꿀 필요가 없고, 테스트 자체가 설계의 품질을 드러낸다.
[ createTestingModule() ]
│
▼
┌───────────────────────┐
│ Testing DI Container │
│ ┌─────────────────┐ │
│ │ Provider A │ │
│ │ Provider B │ │ ← Mock / Real 교체 가능
│ │ Provider C │ │
│ └─────────────────┘ │
└───────────────────────┘
│
▼
moduleRef.get(Token)
createTestingModule 은 @Module 데코레이터와 동일한 메타데이터 구조를 받아 격리된 DI 컨테이너를 만든다. compile 을 호출한 뒤 moduleRef.get 으로 인스턴스를 꺼낼 수 있다.
TestingModule — @nestjs/testing이 제공하는 테스트 전용 NestJS 모듈. 실제 HTTP 서버 없이 DI만 구동한다.
의존성을 Mock Provider로 교체해 클래스 하나의 로직만 검증한다.
// cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
import { CatsRepository } from './cats.repository';
describe('CatsService', () => {
let service: CatsService;
const mockRepo = { findAll: jest.fn().mockResolvedValue([]) };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CatsService,
{ provide: CatsRepository, useValue: mockRepo },
],
}).compile();
service = module.get<CatsService>(CatsService);
});
it('고양이 목록을 반환한다', async () => {
const result = await service.findAll();
expect(mockRepo.findAll).toHaveBeenCalledTimes(1);
expect(result).toEqual([]);
});
});
useValue로 실제 Repository 대신 Mock 객체를 주입하면 DB 연결 없이 Service 로직만 테스트할 수 있다.
Mock Provider — useValue / useFactory / useClass 로 실제 구현체를 대체하는 테스트 전용 Provider.
여러 Provider를 함께 구동해 모듈 경계를 검증한다. 실제 TypeORM Repository 대신 인메모리 DB를 연결하거나, jest.spyOn으로 외부 호출만 차단하는 방식이 일반적이다.
| 전략 | 장점 | 단점 |
|---|---|---|
| 인메모리 DB (SQLite) | DB 로직까지 검증 | 방언 차이 발생 가능 |
| Mock Repository | 빠른 실행 | SQL 쿼리 미검증 |
| 실제 DB + 트랜잭션 롤백 | 정확도 최고 | 환경 의존성 높음 |
@nestjs/testing과 supertest를 결합해 HTTP 계층 전체를 검증한다.
// cats.e2e-spec.ts
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('Cats (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('GET /cats → 200', () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200);
});
afterAll(() => app.close());
});
createNestApplication을 호출하면 실제 HTTP 서버가 기동된다. supertest는 포트 없이 내부 소켓으로 요청을 보내므로 포트 충돌이 없다.
supertest — HTTP 서버를 실제로 listen하지 않고 내부 소켓으로 요청을 전달하는 테스트 유틸리티.
NestJS CLI가 생성하는 기본 jest 설정은 package.json에 포함된다.
{
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": { "^.+\\.(t|j)s$": "ts-jest" },
"collectCoverageFrom": ["**/*.(t|j)s"],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
E2E 테스트는 별도 test/jest-e2e.json을 두고 rootDir를 test로 지정하며, testRegex를 \\.e2e-spec\\.ts$로 변경한다. npm run test:e2e로 분리 실행하는 것이 관례다.