NestJS + TypeORM + pg-mem 사용하기

이준영·2024년 4월 30일
0

CI등의 테스트 환경에서 PostgreSQL를 설치하지 않고 테스트하고 싶은 경우가 생길 수 있다

이때 pg-mem을 사용하면 PostgreSQL을 설치하지 않고 TypeORM 사용이 가능하다

먼저 pg-mem을 설치한다 npm i pg-mem --save

TypeOrmModule은 dataSourceFactory라는 기능을 제공하고 있다 해당 기능을 이용해서 pg-mem에서 생성한 datasoruce를 넘겨주도록 하자

TypeOrmModule.forRootAsync({
  useFactory: () => {
    return {
      type: 'postgres',
      host: 'your-host',
      database: 'your-database-name',
      username: 'your-username',
      password: 'your-password',
      entities: ['your-entity-file-path or entity class'],
    };
  },
  // useFactory에서 반환된 options값이 dataSourceFactory의 매개변수로 전달됩니다.
  dataSourceFactory: (options) => {
    const memoryDb = newDb();
    const dataSoruce = memoryDb.adapters.createTypeormDataSource({
      ...options,
      // test 환경에 테이블이 자동으로 생성되도록 Synchronize를 true로 설정합니다.
      synchronize: true,
    });

    return dataSoruce;
  },
})

이 상태에서 서버를 실행하면 아래와 같은 에러가 호출된다

Failed SQL statement: SELECT version();;

SELECT version(); 이라는 쿼리를 TypeORM에서 실행하고 있는데 pg-mem에서 지원하지 않기 때문이다

TypeORM에서 왜 버전을 체크하는지 확인 해보니 isGeneratedColumnsSupported 를 처리하기 위함으로 보인다

현재 사용하고있는 PostgreSQL 버전에 맞춰 pg-mem에 추가 설정을 아래와 같이 하자

memoryDb.public.registerFunction({
  name: 'version',
  implementation: () => 'PostgreSQL 16.1', // 버전 확인
});

다시 서버를 실행하면 에러가 또 발생하게 된다

Failed SQL statement: SELECT * FROM current_database();

current_database()를 호출하는 이유는 현재 접속된 database이름을 가져오려는 목적으로 보여 접속되는 데이터 베이스 이름이 반환되도록 options에서 설정한 databaseName을 반환하게 아래처럼 설정한다

 memoryDb.public.registerFunction({
   name: 'current_database',
   implementation: () => options.database, 
 });

options에 있는 값이 아닌 별도의 값으로 세팅해도 좋다

다시 서버를 실행하면 에러가 또 발생한다

Failed SQL statement: SELECT "table_schema", "table_name", obj_description
(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ("table_schema" = 
'public' AND "table_name" = 'test_entity');

해당 쿼리는 entities: [] 에 등록된 entity가 현재 table이 존재하는지 확인하려는 의미로 보인다 pg-mem을 사용하기에 해당 쿼리에 반환값은 빈 배열이 되어야 한다

아래와 같이 해당 쿼리가 들어오면 빈 결과를 반환하도록 설정한다

memoryDb.public.interceptQueries((query) => {
  if (
    query.includes(
      `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE`,
    )
  ) {
    return [];
  }

  return null;
});

SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE 이 쿼리는 TypeOrm 버전이 변경되면서 변경될 가능성이 있어 TypeOrm 버전에 따라 적절한 조건값을 설정해야 한다 현재는 0.3.20 버전 기준

이제 다시 서버를 실행하면 정상적으로 실행되게 된다 👍👍👍

최종 코드

TypeOrmModule.forRootAsync({
  useFactory: () => {
    return {
      type: 'postgres',
      host: 'your-host',
      database: 'your-database-name',
      username: 'your-username',
      password: 'your-password',
      entities: [TestEntity],
    };
  },
  // useFactory에서 반환된 options값이 dataSourceFactory의 매개변수로 전달됩니다.
  dataSourceFactory: (options) => {
    const memoryDb = newDb();

    memoryDb.public.registerFunction({
      name: 'version',
      implementation: () => 'PostgreSQL 16.1', // 버전 확인
    });

    memoryDb.public.registerFunction({
      name: 'current_database',
      implementation: () => options.database, // options에 있는 값이 아닌 별도 설정도 가능
    });

    memoryDb.public.interceptQueries((query) => {
      if (
        query.includes(
          `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE`,
        )
      ) {
        return [];
      }

      return null;
    });

    const dataSoruce = memoryDb.adapters.createTypeormDataSource({
      ...options,
      // test 환경에 테이블이 자동으로 생성되도록 Synchronize를 true로 설정합니다.
      synchronize: true,
    });

    return dataSoruce;
  },
})
profile
NodeJS 백엔드 개발자

0개의 댓글