728x90

pg-mem을 Nest 프로젝트의 Test DB로 적용하기로 한 이유

https://springdream0406.tistory.com/224

 

Test Code 2 (작성 중)

통합테스트 코드를 작성해본 후, 대부분을 mock으로 처리해놓은 현재 유닛테스트에 대한 의문을 가지고 있던 중, https://tech.inflab.com/20230404-test-code/ 테스트 코드를 왜 그리고 어떻게 작성해야 할

springdream0406.tistory.com


과정

우선 간단하게 gpt에게 적용방법을 물어봤다.

설치 방법과 간단한 기본 세팅을 알려주길래, 그 중 db.public.registerFunction의 기능에 대해 물어봤고, 사용자가 커스터마이징한 쿼리함수를 사용하게 해주는 기능이라고 답변을 해주었다.

나는 커스텀한 쿼리를 쓸일이 없으니 필요없는거 아니냐고 하니깐 이를 제외하고 나머지 코드들을 알려주었다. (오류의 발단)

나는 이를 바로 적용하지 않고, 다른 프로젝트들도 세팅을 해야하기 때문에 함수형식으로 바꿔서 쉽게 적용할 수 있도록 변경을 요청했고, gpt에게 요청하고 부분부분 질문을 하면서 코드가 변경되고나니 나중에는 질문마다 gpt의 대답이 계속 바뀌게 되었다.

 

대표적으로 dataSource에 작성하는 여러 항목들에 대하여 필요없다고했다가 필요하다고 했다가 자신의 대답에 부정하는 믿을 수 없는 상태가 되어서 결국 인터넷을 찾아보았고, gpt가 왜 대답을 제대로 못하는지 알게 되었다.

gpt는 학습이 필요한데 인터넷에 nest pg-mem을 검색했을 때 나오는 글이 몇개 없다.. 결국 학습이 제대로 안되어서 정보가 부족하다보니 제대로 대답을 못하는 것이었다는 생각이 든다.

 

무튼 얼마 없는 정보중에 2개의 글을 추려서 이를 따라 적용해보았다. (글의 끝에 참고글로 링크를 첨부해두었다.)

 

우선 첫 글은 세팅과 적용 코드가 있었는데, 세팅에는 앞서 gpt에게 나는 필요없지 않냐고 물어봤던 registerFunction이 많이 적혀있고, 적용하는 module부분도 Test용 모듈이 아닌 실제 모듈에 조건을 걸어서 변경하는 방법을 적용해놓았다보니 현재 나의 상황과 먼가 맞지 않는 느낌이 들었다.

그래서 필요하다 생각하는 부분들만 가져다 적용했더니 에러가 났다.

대표적으로 앞서 registerFunction이 필요없다고 생각했었는데 current_databaseversion은 기본적으로 설정을 해주어야지 안그러면 없다고 에러가 난다..

그래서 나와 상황이 다른 코드를 그대로 다 적용하기는 싫어서 두번째 정보글을 따라해보았다.

 

2번째 글의 경우 자신이 적용하면서 만난 문제들과 그것들을 해결한 방식으로 적혀있었다.

결과적으로 작성한 코드가 현재 내 테스트와 비슷하여 이를 적용했고, 잘 동작했다. 라고 생각했다.

 

pg-mem의 적용을 테스트하기 위해 기본적인 테스트코드를 작성했었고, 구동시 에러가 없는걸 확인 후, 코드를 복붙하여 1번 테스트에서 user 레퍼지토리에 데이터를 넣고 이를 조회하는 코드를 작성하고, 2번 테스트에서는 user 레퍼지토리를 조회만 하는 코드를 작성했다.

그리고 테스트를 돌려보니 입력이 없는 2번 테스트에서 user가 조회되는걸 보고 둘이 db를 공유하고 있다는걸 알게되고, pgAmin으로 조회해보니 실제 test db에 데이터가 들어가고 있었다. 그래서 database: test 에서 test1으로 바꿨더니 찾을 수 없다고 에러가 떴다.

이를 통해 잘 적용되었다고 생각했던 2번 글의 경우 실제 db와 연결되어서 제대로 작동하는 코드가 아니였다. (아마 중간에 있는 Connection이 더는 지원하지 않는 코드라고 뜨던데 그부분 때문인듯 싶고, 아니면 내가 잘못 적용한 걸 수도 있다.)

 

그래서 다시 1번 정보글로 가서 pg-mem의 세팅방법을 그대로 가져오고, 적용방법만 나에게 맞게 조금 변경 후, 이를 구동시켜보니 에러 없이 잘 되었으며, db가 연동되는 일 또한 없었다.

결국 1번 정보글의 내용을 따라하지만, 이제 나에게 필요없는 세팅이 있는지 확인 후 이를 제거해나아가야한다.

 

++ 글을 쓰면서 2번 정보글에서 문제가 되었다고 생각했던 부분을 gpt에게 물어보니 datasource로 대체하면 된다고 답변을 주었다.

2번 글의 경우 22년에 작성된 글이다보니 해당 함수들을 지원하지 않았고, 이를 DataSource로 대체하니 원하는대로 동작을 했다.

그래서 최종적으로 2번글에서 지원안하는 함수를 바꿔서 적용하게 되었다.

 

그렇게 최종적으로 2번글을 참고한 후, 강연에서처럼 트랜젝션을 사용해보려고 했지만, rollback이 되지 않아서 고생고생하다가 결국 2번글에서 사용한 backup과 restore기능을 사용하기로 했다.

rollback이 안되기도 했지만, 내 실제 코드들이 쿼리러너로 작성된게 아니라서 현재 내 지식으로는 프렌젝션으로 테스트를 할수도 없는 상태이다.

 

++ 실제 테스트에 적용해보니 2번글의 내용만으로는 진행이 안되었다. uuid에러가 나고 해결하니 쿼리 에러같은게 나는걸 보고 결국 1번글의 세팅에 있는 세팅을 가져와서 적용했다.


결과

// initPgMem.ts

import { randomUUID } from 'crypto';
import { DataType, IMemoryDb } from 'pg-mem';
import { DataSource, EntityTarget } from 'typeorm';

export const initPgMem = async (
  name: string,
  db: IMemoryDb,
  entities: EntityTarget<any>[],
): Promise<DataSource> => {
  //
  db.public.registerFunction({
    name: 'current_database',
    implementation: () => 'test',
  });

  db.public.registerFunction({
    name: 'version',
    implementation: () => 'test',
  });

  db.registerExtension('uuid-ossp', (schema) => {
    schema.registerFunction({
      name: 'uuid_generate_v4',
      returns: DataType.uuid,
      implementation: randomUUID,
      impure: true,
    });
  });

  db.public.interceptQueries((sql) => {
    const newSql = sql.replace(/\bnumeric\s*\(\s*\d+\s*,\s*\d+\s*\)/g, 'float');
    if (sql !== newSql) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
      return db.public.many(newSql);
    }

    return null;
  });

  db.public.interceptQueries((queryText) => {
    if (queryText.search(/(pg_views|pg_matviews|pg_tables|pg_enum)/g) > -1) {
      return [];
    }
    return null;
  });

  const dataSource = await db.adapters.createTypeormDataSource({
    type: 'postgres',
    database: `${name} test`,
    entities,
    synchronize: true,
  });

  await dataSource.initialize();

  return dataSource;
};

혹시나 있을지 모르는 테스트끼리 database 공유 상황을 방지하고자 name을 넘겨받아서 database에 사용했다.

backup 기능을 사용하기 위해 db를 넘겨받는 식으로 만들었다.

entities는 테스트에 따라 변경되니 넘겨받아서 넣는 방식을 사용했다.

 

오류가 나지 않는 최소한의 registerFunction들을 적어주었다. 안적어주면 해당 기능없다고 오류가 난다..

++ 오류가나서 나머지 세팅들도 추가해주었다.

 

// test.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { IBackup, newDb } from 'pg-mem';
import { Role, User } from 'src/apis/02.User/entity/user.entity';
import { UserService } from 'src/apis/02.User/user.service';
import { Solar } from 'src/apis/03.SPP/entity/solar.entity';
import { initPgMem } from 'src/common/config/initPgMem';
import { DataSource, Repository } from 'typeorm';

describe('UserService', () => {
  let service: UserService;
  let repository: Repository<User>;
  let backup: IBackup;

  beforeAll(async () => {
    const db = newDb();
    const entities = [User, Solar];
    const dataSource = await initPgMem('userService', db, entities);

    const module: TestingModule = await Test.createTestingModule({
      imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature(entities)],
      providers: [UserService],
    })
      .overrideProvider(DataSource)
      .useValue(dataSource)
      .compile();

    service = module.get<UserService>(UserService);
    repository = dataSource.getRepository(User);

    backup = db.backup();
  });

  afterEach(async () => {
    backup.restore();
    console.log('restore');
  });

  describe('', () => {
    it('', async () => {
      console.log('1', await repository.find());
      await repository.save({ role: Role.USER });
      console.log('2', await repository.find());
    });

    it('', async () => {
      console.log('3', await repository.find());
    });
  });
});

entities는 자신의 테스트에 사용되는 entities를 넣어주면 된다.

테스트 코드는 restore로 테스트간에 db 침범이 없는지 확인하는 코드이다.

 

만약 이 글을 참고하여 처음 테스트한다면 위 테스트코드 파일을 복붙하고,

      await repository.save({ role: Role.USER });

부분을 제거 후 같이 돌려봐서 다른 테스트와 db 침범이 없는지 확인해보길 바란다.


참고 글

https://velog.io/@hyob/NestJS-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1

 

NestJS 테스트 환경 구성

테스트 코드 작성시, 외부 API 서비스나 레포지토리 메소드를 모킹해서 사용하곤 한다.orm 기능을 매번 모킹해서 테스트 코드를 짜는것은 로직이 커질 수록 힘들어 진다.sqlite 타입의 in memory db 를

velog.io

 

https://velog.io/@dev_leewoooo/TypeORM-Postgres-Test%ED%95%98%EA%B8%B0with-pg-mem

 

TypeORM + Postgres Test하기(with pg-mem)

TypeORM + Pg를 In-Memory로 테스트해보자 :)

velog.io

 

728x90

'코딩 > 문제&에러' 카테고리의 다른 글

Github Action Test 파일 찾을 수 없음  (0) 2024.11.29
Docker compose postgres 환경변수 처리  (0) 2024.11.24
Test Code의 module  (1) 2024.11.13
TypeORM CASCADE  (0) 2024.11.12
Nest e2e test DB PK 초기화  (3) 2024.11.11

+ Recent posts