NestJs TypeORM 테스트 코드 실행 환경 구축
테스트코드로 프로젝트를 더 안전하게
  • JavaScript

이번에 NestJS + TypeORM 을 사용해 프로젝트를 구축하고 있습니다. 최근 프로젝트에 테스트 코드 작성의 필요성을 고민하게 되었고 테스트 코드 실행 환경을 구축한 내용을 기록해 보고자 합니다.

테스트 코드 실행환경 구성

앞의 테스트 케이스가 다음 테스트 케이스에 영향을 주어서는 안된다

테스트케이스를 구성할 때 주의해야 할 점은 앞의 테스트케이스가 뒤의 테스트케이스에 영향을 주어서는 안된다입니다. (반대 경우에도 마찬가지)

test('test1', () => {
  fooDao.update(1, { value: 2 });
	expect(fooDao.getById(1).value).toBe(2); // OK
}

test('test2', () => {
	expect(fooDao.getById(1)).toBe(1); // OK
}

예를 들어 위 test1, test2의 2개의 테스트케이스의 경우 실행 순서에 따라 어떤 건 성공하고, 어떤 건 실패하면 안 됩니다. 그래서 우리는 Fixture라는 것을 이용해 이를 해결합니다.

Fixtures

  • Fixture란
    • Test Fixture : 고정되어 있는 물체 를 의미
    • 테스트 실행을 위해 베이스라인으로서 사용되는 객체들의 고정된 상태
    • 테스트를 위한 기본으로 세팅된 데이터 구성
beforeEach(() => {
	setFixtures(...);
});

테스트 코드를 실행할 때 beforeEach를 통해 테스트 코드가 실행되기 전 작업을 명시할 수 있습니다. beforeEach에서 setFixtures 함수를 구성해 줌으로써 테스트케이스가 실행되기 전 정해둔 데이터로 db를 초기화할 수 있습니다. 그럼 모든 테스트는 동일한 데이터를 가지고 테스트 케이스가 실행될 수 있습니다.

그리고 테스트가 끝난 후 afterEach에서 사용한 데이터를 싹 날려줍니다.

afterEach(() => {
  truncate all tables..
})

(함수 내용은 구현하지 않았습니다. 느낌만..)

코드로 살펴보면 대략 이런 모습일 것입니다.

// test.spec.ts
setFixtures([[FooRepository, FooEntities]]);

// setFixtures 내부
// beforeEach
await repository.runTransaction(async (tx) => {
  await repository.rawQuery('set foreign_key_checks = 0', [], tx);
  await repository.insert(JSON.parse(JSON.stringify(data)), tx);
  await repository.rawQuery('set foreign_key_checks = 1', [], tx);
});

// afterEach
await repository.runTransaction(async (tx) => {
    await repository.rawQuery('set foreign_key_checks = 0', [], tx);
    await repository.rawQuery(`truncate ${repository.getTableName()}`, [], tx);
    await repository.rawQuery('set foreign_key_checks = 1', [], tx);
  });
}

데이터를 insert, truncate 하기전에 set foreign_key_checks 해주는 이유는 fk를 무시하기 위해서 입니다.
A테이블의 데이터만 테스트하면 되는데, fk로 인해 B테이블까지 데이터를 구성해야하는 일을 막기위해서 입니다.

Docker를 이용한 도컬 mysql 설치

Why Docker?

  • 왜 local에 mysql을 설치하지 않나요?
  • 왜 dev서버에 db로 연결하지 않나요?

우선 꼭 docker가 아니어도 됩니다. 로컬에 mysql을 직접 설치해도 됩니다. 다만 테스트코드를 실행하는 자는 꼭 mysql의 설치에 익숙한 백엔드 개발자가 아닐 수 있음에 주의해야 합니다.
프론트엔드 개발자가 테스트코드를 실행할 경우가 있고, 그런경우라면 프론트엔드 개발자도 mysql설치를 해야합니다.
(지금 글을 읽고있는분이 프론트엔드 개발자라고 하더라도 mysql정도는 설치/사용할 줄 알면 좋습니다.)

보통 E2E라고 하면 controller <—> db 까지의 테스트라고 생각할 수 있지만, 좀 더 크게 E2E를 본다면 프론트엔드 <—> 백엔드까지의 범위라고 볼 수 있습니다.
그런경우 프론트에서 특정 버턴을 누르고 api 가 호출되어 db에 데이터가 들어가는 것 까지 테스트할 수 있습니다.
(js에서 사용하는 도구로는 cypress와 같은게 있습니다.)

이런경우를 생각해 Docker로 설정하였습니다.
그리고 웹서비스를 한다면 nginx설정도 해야할 경우가 있는데, 이런 경우에도 docker 로 구성해두면 더욱 편리합니다.

ex) docker-compose.yaml

version: '3'
services:
mysql:
image: mysql:8.0
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --init-file /init-test-db.sql
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE={{databaseName}}_test
- LANG=C.UTF-8
volumes:
- ./docker/mysql/data:/var/lib/mysql
- ./docker/mysql/init-test-db.sql:/init-test-db.sql
ports:
- '3306:3306'

전체 동작을 그림으로 보면 아래와 같습니다.

image