NestJS를 쓰면서 테스트 코드를 생각한다면 자연스럽게 jest 기반 테스트 환경이 따라온다.
이번엔 뜨도록 프로젝트가 어느정도 안정되어 미루고 미뤄뒀던 테스트 코드 작성을 끝낸 기념으로 이 글을 작성한다.
근데 생각보다 복잡하다. 내경우에 postgres, mongoDB를 사용중인데,
- -
SQLite썼다가 타입 오류에 머리 싸매고 - - Mongo는 또 Mongo대로
memory-server세팅이 다르고...
그래서 정리했다.
의존성 설치부터 pg-mem + mongodb-memory-server까지.
기본 테스트 환경 구성
설치
npm install --save-dev jest @nestjs/testing ts-jest @types/jest- -
jest: 테스트 실행기 - -
@nestjs/testing: NestJS 전용 유닛테스트 헬퍼 - -
ts-jest: TypeScript로 작성된 테스트를 jest에서 돌릴 수 있게 함
만약 Mongoose도 함께 사용한다면:
npm install --save-dev mongodb-memory-serverest.config.ts 설정
// jest.config.ts
export default {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testRegex: '.*\\.spec\\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: ['src/**/*.(t|j)s'],
coverageDirectory: './coverage',
testEnvironment: 'node',
};Nest CLI 쓰면 자동으로 생성되지만 커스텀 설정할 땐 꼭 확인 필요.
test용 환경 설정
// test/jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"testEnvironment": "node"
}E2E용 설정은 따로 관리하는 게 좋다.
E2E: End to End로 "사용자가 사용하는 상황을 가정하고 테스트 하는 것"을 의미한다.
유닛 테스트 예시
describe('UserService', () => {
let service: UserService;
let repo: Repository<User>;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [UserService],
}).compile();
service = moduleRef.get<UserService>(UserService);
});
it('회원 생성 테스트', async () => {
const result = await service.createUser({ name: '철수' });
expect(result.name).toBe('철수');
});
});E2E 테스트 예시
describe('Auth (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('/auth/login (POST)', () => {
return request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'test@test.com', password: '1234' })
.expect(201);
});
});PostgreSQL 테스트에서 삽질했던 이야기
유닛 테스트는 mock으로도 충분히 커버되지만, E2E 테스트에서는 실제 DB까지 통합 검증하고 싶어서 sqlite in-memory로 먼저 설정했었음.
처음엔 typeorm + sqlite 조합으로 테스트하려 했는데...
@Column({
type: 'enum',
enum: MyEnum,
})
myField: MyEnum;이게 SQLite에서는 안 먹힌다.
심지어 uuid_generate_v4() 같은 함수도 없어서 에러 터짐.
그래서 다음과 같은 에러를 보게 된다:
no such function: uuid_generate_v4
해결책은?pg-mem으로 가자.
pg-mem으로 PostgreSQL 완벽 대체
설치
npm install pg-mem사용 이유:
-
SQLite는PostgreSQL과 호환되지 않는 기능이 너무 많아서, 실제와 유사한 환경이 필요했고-
pg-mem은PostgreSQL을 JS로 시뮬레이션하는 in-memory DB라서 실제enum,uuid,array등의 기능을 테스트할 수 있음- 물론 실제
PostgreSQL과 100% 동일하게 동작하지는 않지만 테스트코드 작성시엔 도움이 된다.사용 예시:
import { newDb } from 'pg-mem';
export function createInMemoryDataSource(entities: any[]): DataSource {
const db = newDb({ autoCreateForeignKeyIndices: true });
db.public.registerFunction({
name: 'uuid_generate_v4',
args: [],
implementation: () => '00000000-0000-0000-0000-000000000000',
});
return db.adapters.createTypeormDataSource({
type: 'postgres',
entities,
synchronize: true,
});
}· pg-mem은 실제 PostgreSQL 기능을 흉내내는 in-memory DB
· TypeORM 호환, 속도 빠르고 안정적
· enum, uuid, array 등 다 된다!
주의할 점
· test 환경에서만 사용
실제 PostgreSQL과 100% 같진 않으니 일부 기능은 제한적일 수 있음
MongoDB는? → mongodb-memory-server
Mongo는 따로!mongodb-memory-server를 쓰면 깔끔하게 해결된다.
import { MongoMemoryServer } from 'mongodb-memory-server';
let mongod: MongoMemoryServer;
beforeAll(async () => {
mongod = await MongoMemoryServer.create();
const uri = mongod.getUri();
await mongoose.connect(uri);
});
afterAll(async () => {
await mongoose.disconnect();
await mongod.stop();
});NestJS에서 MongooseModule.forRoot(uri)에 연결해주면 끝.
마무리
스프링에서 H2 같은 DB를 쓰다가 실제 운영 DB에서 오류 나는 경험이 있다면 이해될 거다. NestJS도 마찬가지로, 가짜 SQLite로 테스트할 바엔 pg-mem으로 PostgreSQL 환경을 흉내 내는 게 훨씬 신뢰도 높다.
'Nest.js' 카테고리의 다른 글
| [NestJS - 트러블 슈팅] 몽고 DB replicaSet 없이는 트랜잭션 불가능 (4) | 2025.06.19 |
|---|---|
| NestJS 엔티티, 왜 다 public이야? – Spring 개발자의 궁금증 해결 (1) | 2025.05.25 |
| [NestJS 인증 흐름] Jwt 토큰 기반 인증 요청 흐름 정리 (1) | 2025.05.22 |
| [NestJS 응답/요청 변환기] 내부 camelCase, 외부 snake_case 통일하기 (0) | 2025.05.22 |
| [Nest.js] 제로베이스에서 1주일, 실전까지 반나절 (1) | 2025.05.22 |