icon

E2E 테스트 구현


 E2E(End-to-End) 테스트는 애플리케이션의 전체 흐름을 사용자 관점에서 검증하는 테스트 방식입니다.

 단위 테스트가 개별 컴포넌트를, 통합 테스트가 컴포넌트 간 상호작용을 검증한다면 E2E 테스트는 실제 사용 시나리오를 모방하여 시스템 전체의 동작을 확인합니다.

E2E 테스트의 중요성

  1. 사용자 경험 검증 : 실제 사용자 시나리오를 테스트
  2. 시스템 통합 확인 : 모든 컴포넌트가 함께 올바르게 작동하는지 검증
  3. 회귀 테스트 : 새로운 기능 추가 시 기존 기능의 정상 작동 확인

NestJS E2E 테스트 환경 설정

  1. 의존성 설치
npm install --save-dev @nestjs/testing supertest
  1. Jest 설정
test/jest-e2e.json
{
  "moduleFileExtensions": ["js", "json", "ts"],
  "rootDir": ".",
  "testEnvironment": "node",
  "testRegex": ".e2e-spec.ts$",
  "transform": {
    "^.+\\.(t|j)s$": "ts-jest"
  }
}
  1. E2E 테스트 파일 생성
test/app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
 
describe('AppController (e2e)', () => {
  let app: INestApplication;
 
  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
 
    app = moduleFixture.createNestApplication();
    await app.init();
  });
 
  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
 
  afterAll(async () => {
    await app.close();
  });
});

HTTP 요청 시뮬레이션과 응답 검증

 Supertest를 사용한 GET 요청 테스트

it('/users (GET)', () => {
  return request(app.getHttpServer())
    .get('/users')
    .expect(200)
    .expect('Content-Type', /json/)
    .expect(res => {
      expect(Array.isArray(res.body)).toBeTruthy();
      expect(res.body.length).toBeGreaterThan(0);
    });
});

 POST 요청 테스트

it('/users (POST)', () => {
  return request(app.getHttpServer())
    .post('/users')
    .send({ name: 'John Doe', email: '[email protected]' })
    .expect(201)
    .expect(res => {
      expect(res.body.id).toBeDefined();
      expect(res.body.name).toBe('John Doe');
    });
});

데이터베이스 연동 E2E 테스트

 테스트 데이터베이스 설정

import { TypeOrmModule } from '@nestjs/typeorm';
 
const moduleFixture: TestingModule = await Test.createTestingModule({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: ':memory:',
      entities: [/* 엔티티 목록 */],
      synchronize: true,
    }),
    AppModule,
  ],
}).compile();

 테스트 데이터 관리

import { getRepository } from 'typeorm';
import { User } from './user.entity';
 
beforeEach(async () => {
  const userRepository = getRepository(User);
  await userRepository.clear(); // 테스트 전 데이터 초기화
  await userRepository.save([
    { name: 'Test User 1', email: '[email protected]' },
    { name: 'Test User 2', email: '[email protected]' },
  ]);
});

인증이 필요한 엔드포인트 테스트

 JWT 인증 테스트

let authToken: string;
 
beforeAll(async () => {
  const response = await request(app.getHttpServer())
    .post('/auth/login')
    .send({ username: 'testuser', password: 'testpass' });
  authToken = response.body.token;
});
 
it('/protected (GET)', () => {
  return request(app.getHttpServer())
    .get('/protected')
    .set('Authorization', `Bearer ${authToken}`)
    .expect(200);
});

비동기 작업 테스트

 긴 실행 시간을 가진 프로세스 테스트

it('should handle long running process', async () => {
  const response = await request(app.getHttpServer())
    .post('/long-process')
    .timeout(10000) // 타임아웃 설정
    .expect(202);
 
  expect(response.body.jobId).toBeDefined();
 
  // 작업 완료 확인
  await new Promise(resolve => setTimeout(resolve, 5000));
 
  const result = await request(app.getHttpServer())
    .get(`/long-process/${response.body.jobId}`)
    .expect(200);
 
  expect(result.body.status).toBe('completed');
});

마이크로서비스 아키텍처 E2E 테스트

 마이크로서비스 모킹

import { ClientsModule, Transport } from '@nestjs/microservices';
 
const moduleFixture: TestingModule = await Test.createTestingModule({
  imports: [
    ClientsModule.register([
      { name: 'USER_SERVICE', transport: Transport.TCP },
    ]),
    AppModule,
  ],
}).compile();
 
app.connectMicroservice({
  transport: Transport.TCP,
  options: { port: 3001 },
});
 
await app.startAllMicroservices();

성능 및 부하 테스트 통합

 Apache JMeter 또는 k6와 같은 도구를 사용한 부하 테스트

import * as k6 from 'k6';
import http from 'k6/http';
 
export default function() {
  http.get('http://localhost:3000/users');
}
 
export let options = {
  vus: 10,
  duration: '30s',
};

E2E 테스트 최적화

  1. 병렬 실행 : Jest의 --maxWorkers 옵션 사용
  2. 데이터베이스 트랜잭션 : 각 테스트 후 롤백
  3. 테스트 격리 : Docker 컨테이너 사용

Best Practices

  1. 실제 환경과 유사한 테스트 환경 구성
  2. 중요한 사용자 흐름에 집중
  3. 데이터 설정과 정리를 철저히 관리
  4. 안정적인 테스트를 위해 적절한 대기 시간과 재시도 로직 구현
  5. CI/CD 파이프라인에 E2E 테스트 통합
  6. 성능 문제 감지를 위한 응답 시간 측정
  7. 테스트 결과 보고서 자동화
  8. 정기적인 테스트 스위트 리뷰 및 업데이트

주의사항

  1. E2E 테스트는 단위 테스트보다 실행 시간이 길 수 있음
  2. 외부 서비스 의존성 관리에 주의
  3. 테스트 데이터의 일관성 유지
  4. 비결정적 요소(타임스탬프, 랜덤 값 등) 처리에 주의
  5. 테스트 환경과 실제 환경의 차이 최소화

 NestJS에서의 E2E 테스트 구현은 애플리케이션의 전체적인 동작을 검증하는 중요한 과정입니다.

 Supertest와 NestJS의 테스팅 모듈을 활용하여 HTTP 요청을 시뮬레이션하고 응답을 검증할 수 있습니다.