들어가며
안녕하세요. 잡채입니다 🙇🏻
얼마 전 사이드 프로젝트 서버 작업을 하며, e2e 테스팅 중 docker
를 사용해 MySQL DB를 완전히 격리해 테스트 할 수 있도록 환경을 세팅해보았습니다.
e2e 테스트 시 애플리케이션의 처음과 끝을 흐름에 따라 모두 테스트 하기 때문에 당연히 DB 접근을 해야합니다.
현재 프로젝트의 경우 개발용 MySQL DB 를 RDS에 올려 사용 중인데 아무리 개발용 데이터베이스라 하여도 여러번 진행되는 e2e 테스트 마다 테스트용 데이터들이 쌓이거나 바뀌도록 구성할 수는 없었습니다.
또한, 현재 사용중인 데이터를 잘못하여 update 시키거나 삭제할 수도 있기 때문에 e2e 테스트 중에는 격리된 데이터 베이스를 새로 구성하고 싶었습니다.
여러 자료를 찾아보던 중 prisma 공식 가이드에 docker 를 사용한 테스트 가이드가 있어 참고하며 테스트 환경을 구축해보았습니다.
구성 방식
테스트 환경을 구성하기 위한 방식은 요약하자면 아래와 같습니다.
e2e 테스트 진행 시 docker 를 활용해 MySQL 컨테이너 실행
prisma migrate 진행
미리 만들어둔 seed 파일을 통해 더미 데이터 삽입
jest e2e 테스트 진행
컨테이너 종료, 삭제
방식은 정말 심플합니다!
테스트마다 MySQL 컨테이너를 만들고, Prisma 스키마 마이그레이션 후 더미데이터 seed 파일을 삽입하여 테스트 후 컨테이너를 삭제하는 작업입니다.
테스트 진행 흐름에 따라 구성을 시작해보겠습니다.
docker 세팅
로컬에서 테스트 시 컨테이너를 생성, 실행하기 때문에 docker
, docker-compose
가 설치되어 있어야합니다.
설치되어 있지 않다면 brew
로 간단히 설치해주세요. (mac OS 유저라면)
brew install cask docker
brew install docker-compose
이제 docker-compose
를 사용해 MySQL 컨테이너를 생성/실행하는 구성 방식이 담긴 compose 파일을 작성하면 됩니다.
저는 기본 앱 컨테이너화를 위한 compose 파일이 있어 docker-compose.test.yml
파일로 생성했습니다.
# Set the version of docker compose to use
version: '3.9'
# The containers that compose the project
services:
db:
image: mysql:8.0
restart: always
container_name: e2e-test-prisma
ports:
- '3306:3306'
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
volumes:
- /var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
compose 파일 구성은 다음과 같습니다.
MySQL 8.0 이미지를 사용해 컨테이너를 생성합니다.
포트의 경우 MySQL 은 기본 3306을 사용하기 때문에 Host OS 와 port를 바인딩 시켜 주었습니다.
MySQL root password, database 는 알아서 생성해주시면 됩니다.
저는 테스트 용이기 때문에 root 로 명시했습니다.
아래 command 는 한글 설정을 위한 작업입니다. 이 설정 없이 컨테이너를 생성하면 테이블에 들어간 한글 데이터가 깨져서 나올 수 있습니다.
compose 파일을 통해 컨테이너를 실행하려면 아래 명령어를 입력해보면 됩니다.
docker-compose -f docker-compose.test.yml up -d
docker ps
정상적으로 컨테이너가 만들어졌다면 docker ps
로 컨테이너 확인이 가능합니다.
컨테이너 내부 MySQL 로 접속하고 싶다면 아래 명령어를 사용하시면 됩니다.
docker exec -it ${name} mysql -u root -p
.env 세팅
기존에 사용하던 env 의 database url 은 개발용 DB url 이기 때문에 테스트 환경에서는 localhost DB 로 연결이 필요했습니다.
test 환경에서만 사용한 .env.test
를 새로 생성해주었습니다.
# test database
DATABASE_URL="mysql://root:root@localhost:3306/test?schema=prisma"
... 중략
해당 database url 로 prisma 가 연결하기 때문에 테스트 시에는 아까 생성한 컨테이너로 연결 되도록 주소를 지정해줍니다.
로컬에 띄운 컨테이너 DB는 localhost:3306/${database name}
로 연결하면 됩니다.
.env.test
를 세팅했다면 테스트 환경에서는 .env.test
를 사용할 수 있도록 Nest.js 코드도 살짝 손봤습니다.
서비스에서 환경변수 관리를 @nestjs/config
를 통해 하고 있었기 때문에 app.module.ts
에서 ConfigModule
설정을 바꿔주었습니다.
envFilePath
를 NODE_ENV
에 따라 설정하도록 수정했습니다.
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env',
}),
이제 만들어진 컨테이너에 prisma migrate 을 진행해봅니다.
prisma 에서도 multi env 환경을 구성하기 위한 가이드를 제시해줍니다. 참고하여 dotenv-cli
라는 패키지를 설치했습니다.
yarn add dotenv-cli
dotenv -e .env.test -- npx prisma migrate deploy
dotenv-cli
를 통해 명시적으로 .env.test
를 사용한다는 명령어를 사용하면, prisma migrate 시 위에서 지정한 localhost DB 로 migrate 이 진행됩니다.
Prisma seed
prisma migrate 이 완료된 DB 의 테이블 상태는 어떨까요?
정상적으로 사용중인 테이블이 생성되었고 테이블 내부를 들여다보면,
이 처럼 아직 아무 데이터가 없는 Empty Set 상태인 걸 볼 수 있습니다.
e2e 테스트를 위해서는 DB에 더미데이터가 있어야겠죠.
이를 위해 Prisma 에선 seed 라는 기능을 제공합니다. (공식 가이드 문서 참고)
seed = 씨앗, 말 그대로 아무것도 없는 데이터베이스에 내가 원하는 데이터를 뿌려주는 거라 생각하면 됩니다.
즉, 데이터베이스에 필요한 시작 데이터들을 넣어주는 기능입니다. seed 를 사용하면 데이터베이스가 새로 생성되어도 언제나 일관된 데이터를 가질 수 있습니다.
prisma 를 사용하면 생기는 /prisma
디렉터리 내부에 seed.ts
파일을 만들어줍니다.
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
prisma 문서에서도 제공하는 기본적인 seed 틀입니다.
이제 main 함수 내에서 prisma 로 여러 데이터들을 create 해주면 됩니다.
async function main() {
const user = await prisma.user.create({
data: {
id: 1,
email: 'ddd',
... 중략
},
});
}
seed 파일을 실제 데이터베이스로 푸시하기 위해 package.json
에 스크립트를 하나 추가해줍니다.
"scripts": {
...중략
"seed:test": "dotenv -e .env.test -- npx prisma db seed",
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
seed 파일은 TypeScript 파일이기 때문에 ts-node
를 사용하여 실행시켜주고, 실제 db 에 푸시를 진행할 때는 npx prisma db seed
라는 명령어를 사용하면 됩니다.
마찬가지로, test 환경에서는 dotenv-cli
를 통해 .env.test
를 사용해 연결하도록 명시했습니다.
seed 명령을 실행하면 다음과 같이 컨테이너 내부에 데이터를 삽입합니다.
정상적으로 동작했는지 컨테이너 내부로 들어가 확인해봅시다.
아까와 달리 Empty Set 이 아닌 데이터가 잘 들어간 모습을 확인할 수 있습니다.
테스트 진행 스크립트 작성
이제 구성은 거의 끝났습니다!
다만 위에 진행한 명령어 플로우를 터미널에 한땀한땀 작성하기엔 너무 귀찮잖아요!
우리에겐 package.json
이 있습니다. scripts 를 연결해 테스트 진행 후 컨테이너 삭제까지 한번에 이어지도록 플로우를 작성해봅시다.
"scripts": {
... 중략
"test:e2e": "yarn docker:up && sleep 6.5 && yarn migrate:test && yarn seed:test && dotenv -e .env.test -- jest --config ./test/jest-e2e.json && yarn docker:down",
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
"seed:test": "dotenv -e .env.test -- npx prisma db seed",
"docker:up": "docker-compose -f docker-compose.test.yml up -d",
"docker:down": "docker-compose -f docker-compose.test.yml down -v"
}
다소 많은 스크립트가 있는데요 하나하나 뜯어봅시다.
위에서 진행한 플로우대로라면 가장 먼저 docker-compose
로 MySQL 컨테이너를 실행합니다.
이 명령어를 docker:up
이란 명령어로 선언했습니다.
반대로 docker 컨테이너를 종료하기 위해 docker:down
명령어를 작성해주었습니다. down 명령어는 테스트 종료 후 실행하면 됩니다.
docker:up
이후 갑자기 sleep
명령이 등장합니다.
이 명령을 실행한 이유는 실제 환경 세팅 도중 docker:up
이후 바로 migrate 을 진행하자 컨테이너에 연결하지 못하는 이슈가 발생했기 때문입니다.
문제 해결을 위한 디버깅 도중 컨테이너 생성 이후 약간의 시간 텀을 두고 migrate 을 진행했을 때 정상 진행되는 것을 발견했습니다.
컨테이너 up 이후 MySQL 환경 구성에 대한 약간의 시간이 필요했으나 바로 migrate 을 진행하여 생긴 문제라 판단했고, sleep 명령어로 시간 텀을 두고 migrate 을 진행하도록 추가했습니다.
이제 위에서 작성한 seed 파일 실행까지 완료하면 e2e 테스트를 위한 모든 환경이 구축되었습니다.
jest 로 테스트를 실행해주면 됩니다. 그 전에 테스트 환경변수가 올바르게 동작하도록 dotenv-cli
를 사용해 실행했습니다.
테스트 종료 후에는 docker:down
명령을 통해 만들어진 컨테이너를 종료하도록 합니다.
e2e 테스트 진행
이제 완성된 명령어로 테스트를 진행해봅니다.
yarn test:e2e
docker 컨테이너 실행 이후 잠깐의 텀을 두고 정상적으로 prisma migration 이 진행됩니다.
migration 성공 이후 seed 삽입이 진행되었습니다. seed 삽입 이후 바로 e2e 테스트가 실행됩니다.
e2e 테스트 성공 이후, 자동으로 docker 컨테이너를 종료/삭제 하고 모든 실행이 끝나는 걸 볼 수 있습니다.
마치며
오늘은 docker 를 활용해 Nest.js 에서 e2e 테스트 시 격리된 DB 환경을 구축하여 테스트하는 방법을 알아보았습니다.
Nest.js, Prisma 스택을 사용하시는 분들께 많은 도움이 되었으면 좋겠습니다.
혹시나 Postgres 를 사용하시는 분도 compose 파일만 바꾸시면 잘 동작할테니 참고하여 구성하시면 되겠습니다.
다음에 또 알찬 글로 돌아오겠습니다.
감사합니다. 🚀
'DEV > Node.js' 카테고리의 다른 글
PR 좀 봐달라고 독촉 하는 Slack app (Feat. Node.js, AWS Lambda) (2) | 2023.02.02 |
---|---|
[Nest.js] Official Document 정리 (Controller) - 2 (0) | 2022.08.07 |
[Nest.js] official document 정리 (설치, controller) - 1 (0) | 2022.08.04 |
node.js, express, typescript 로 S3에 image upload 하기 (Feat. multer, aws-sdk) (1) | 2022.06.11 |
Node.js, MongoDB Change Streams 를 사용한 특정 사용자에게 특정 시간에 FCM 보내기 (5) | 2022.01.21 |