이번에 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라는 것을 이용해 이를 해결합니다.
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을 직접 설치해도 됩니다. 다만 테스트코드를 실행하는 자는 꼭 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'