[TIL] Nest.js e2e Test

김시원·2023년 6월 2일
1

TIL

목록 보기
36/50

📌 Issues encountered

Issue #1

Nest.js에서 e2e test - test용 db 연결 & 매 실행마다 데이터 truncate

Issue #2

병원 데이터 추가 POST API가 우리 어플리케이션에 존재하지 않아서 test db에 병원 데이터를 미리 집어넣어줘야하는 과정이 필요함

📌 What I tried

Issue #1

Nest.js에서 e2e test를 위해 테스트용 DB를 연결하는 과정에서 시간이 좀 걸렸다.

  • 사소한 에러가 있었는데, 다른 모듈들을 import할 때 자동 import extension을 설치해서 어떤 모듈들이 import { smth } from './commons/something'으로 상대 경로로 import되지 않고 import { smth } from 'src/commons/something' 이런식으로 절대 경로로 import되었었는데, e2e test를 돌려보니 여기서 에러가 떴었다. 그래서 모든 절대경로를 상대경로로 변경해주었다.
  1. 처음에는 beforeAll() 함수 안에 테스트 db를 직접 연결해주었다.
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { getConnection } from 'typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

describe('E2E 테스트', () => {
  let app: INestApplication;
  const configService: ConfigService;

  beforeAll(async () => {
    // 테스트 데이터베이스 연결 설정
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot(ConfigValidator),
        AppModule, // 실제 애플리케이션 모듈
        TypeOrmModule.forRoot({
          // 테스트 데이터베이스 연결 정보
          type: 'mysql',
          host: configService.get("DB_HOST"),
          port: configService.get("DB_PORT"),
          username: configService.get("DB_USERNAME"),
          password: configService.get("DB_PASSWORD"),
          database: configService.get("TEST_DB"),
          synchronize: true,
          entities: [__dirname + '/../**/*.entity{.ts,.js}'],
        }),
      ],
    }).compile();

    app = moduleFixture.createNestApplication();

    await app.init();
  });

  // 테스트 케이스 작성
});

=> 이렇게 test db로 직접 연결을 해줬는데 계속 development db로 데이터가 추가되었다.

  1. 이렇게 직접 e2e test file에다가 typeORM db를 설정해주는 건 결국 app.module에서 db 설정해주는 코드와 겹치는 코드가 생기기도 하고, app.module에서 설정해준 그대로를 e2e test file에서 따라하기 위해서 다음과 같이 설정해주었다.
// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot(ConfigValidator), // config 설정을 위해 import
    TypeOrmModule.forRootAsync({
      useClass: MysqlConfigProvider,
    }), // mySQL 연결을 위해 import
    ScheduleModule.forRoot(), // task scheduling을 위해 import
    ReportsModule,
    HospitalsModule,
    RequestsModule,
  ],
})
// app.e2e-spec.ts
beforeAll(async () => {
    // test용 DB 연결
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        AppModule,
        TypeOrmModule.forRootAsync({
          useClass: MysqlConfigProvider,
        }), // test db 연결을 위해 import: .env에서 mode === test로 변경해줘야함
      ],
    }).compile();

    app = moduleFixture.createNestApplication();
  
    // 기타 pipe, filter 추가
})

그리고 나는 .env에 현재 프로젝트의 mode를 기입하는 변수를 추가해주었는데, 원래는 development 모드였는데 e2e test를 수행할 때는 test로 변경해주는 식으로 설정해주었다.

// typeORM.config.ts
createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: this.config.host,
      port: this.config.port,
      username: this.config.username,
      password: this.config.password,
      database:
        this.config.mode === 'test'
          ? this.config.test_database
          : this.config.database,
      entities: [Hospitals, Reports],
      synchronize: this.config.mode === 'test', 
      logging: this.config.mode !== 'production',
    };
  }

=> test db에 잘 연결이 된 것을 확인하였다.

  1. 또 다른 문제가 생겼던게, test db가 다시 실행되어도 기존 test에서 생성했던 데이터들이 truncated되지 않고 남아있던 것이었다.
    synchornize: true를 해주었는데도 truncated가 되지 않았다. synchornize: true는 entity에서 변경사항이 생길때 이를 실제 DB에 바로 반영을 하는 것이지, 데이터를 삭제하는 건 아니었다.

=> gpt: synchronize: true는 기존 데이터베이스의 데이터를 삭제하지는 않습니다. 오직 엔티티 스키마의 변경에 따라 새로운 테이블을 생성하거나 기존 테이블의 구조를 변경하는 역할만 합니다.

그래서, 다음과 같은 옵션을 test 모드일때만 추가될 수 있게 설정하였다.

dropSchema: this.config.mode === 'test',

=> test를 수행할 때마다 테이블 안에 데이터가 다 지워지는 걸 확인하였다.

Issue #2

hospitals table에 직접 쿼리를 써서 값을 집어넣어주려면 repository로 쿼리 메서드에 접근할 수 있으므로 Repository를 import해서 사용하였다.

let hospitalsRepository: Repository<Hospitals>; // Hospitals 데이터를 추가해주는 POST API는 존재하지 않고, DB에 미리 저장해놓기 때문에 직접 db에 접근하기 위해 추가

  beforeAll(async () => {
    // 설정 코드

    // 병원 미리 추가해주기
    hospitalsRepository = moduleFixture.get('HospitalsRepository');
    await hospitalsRepository.query(`
      INSERT INTO hospitals (name, address, phone, available_beds, latitude, longitude)
      VALUES ('가톨릭대학교여의도성모병원', '서울특별시 영등포구 63로 10, 여의도성모병원 (여의도동)', '02-3779-1188', 10, 37.51827233800711, 126.93673129599131);
    `); // 가용 병상이 있는 병원
    await hospitalsRepository.query(`
      INSERT INTO hospitals (name, address, phone, available_beds, latitude, longitude)
      VALUES ('가톨릭대학교여의도성모병원', '서울특별시 영등포구 63로 10, 여의도성모병원 (여의도동)', '02-3779-1188', 0, 37.51827233800711, 126.93673129599131);
    `); // 가용 병상이 없는 병원

=> 병원 데이터가 잘 들어간걸 확인하였다. e2e test code에서 이렇게 하는 방법말고 다른 게 있으면 누가 좀 알려줄분.... (getConnection() 이라고 typeorm library에서 제공해주는 메서드가 있는데 얘가 db와 연결을 해서 직접 쿼리를 날릴 수 있는 메서드인데 deprecated 되었다... InjectRepository()도 없어지고 얘도 없어지고 암튼 비권장되는 메서드들이 많고 nest가 계속 업데이트되는 최근 라이브러리라 자료가 많지 않다 ㅠ)

1개의 댓글

comment-user-thumbnail
2023년 6월 2일

역시 지니어싀언!! 퍼가여~ ㅋㅋ

답글 달기