
상황
Nest.js 서버에서 jest를 사용한 테스트 코드를 작성하고 있었다. 평소에는 아래와 같이 ConfigService를 모의하여 configService.get으로 환경 변수를 처리했다. 그런데 이런 방식이 마음에 들지 않았다. 가장 큰 이유는 필요한 문자열이 하드코딩 되어있어 환경 변수를 바꾸거나 바뀌어야 할 때마다 수작업을 해야 했기 때문이다.
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === 'jwt.accessSecret') return 'accessSecret';
if (key === 'jwt.refreshSecret') return 'refreshSecret';
return null;
}),
},
},따라서 나는 ConfigModule을 사용해 테스트 모듈이 직접 .env.test의 값을 가져다 쓰도록 했다. 이렇게 하면 혹시 모를 보안 사고를 예방할 수 있고, 뭔가를 바꿔야 할 때마다 테스트 코드를 만지는 대신 .env.test만 수정하면 된다. 이론상 모든 것이 완벽했다.
describe('BookResolver', () => {
let resolver: BookResolver;
let jwt: ConfigType<typeof jwtConfig>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: '.env.test',
}),
],
providers: [
BookResolver,
{
provide: jwtConfig.KEY,
useValue: jwtConfig(),
},
],
}).compile();
resolver = module.get(BookResolver);
jwt = module.get(jwtConfig.KEY);
});
});문제
하지만 현실은 시궁창이고, ConfigModule은 .env.test가 아닌 .env의 값을 가져오고 있었다. 뭔가 잘못됐나 싶어서 이리저리 코드를 바꿔보았지만 달라지는 게 없었다. 이 과정에서 한 가지 특이한 현상을 발견할 수 있었다. 루트 디렉토리에 .env가 없으면 .env.test의 값을 제대로 가져오지만, 루트 디렉토리에 .env가 있으면 무조건 .env의 값을 가져오는 것이 아닌가.
따라서 src/config에 .env와 .env.test를 두면 문제 자체는 해결할 수 있을 것이다. 그러나 원인을 모르는 채 현상만을 해결하는 게 영 마음에 들지 않았다. 나는 이 문제의 원인을 찾는데 꼬박 하루가 걸렸다.
일단은 새로운 Nest.js 프로젝트를 하나 만들고 거기서 ConfigModule을 테스트해보았다. 여기서는 루트 디렉토리에 .env가 있어도 문제 없이 .env.test의 값을 가져오고 있었다. 이를 근거로 나는 '어떤 라이브러리가 루트 디렉토리의 .env를 process.env에 덮어쓰고 있다'고 판단했다.
확인해보기 위해 AppModule에 import 되어있는 모듈을 모두 주석처리 해보았다. 그랬더니 .env가 루트 디렉토리에 있어도 테스트 코드는 .env.test의 값을 가져오는 것이 아닌가! 내 가설은 증명되었다. 그러나 추가로 설치한 수많은 라이브러리 중에서 어떤 놈이 문제를 일으키고 있는 건지는 알 수가 없었다.
나는 새로운 모듈을 만들어 라이브러리를 하나씩 추가해보기로 했다. 추가할 때마다 테스트를 돌려서 ConfigModule이 .env 값을 가져오는지 확인했다. 이 지난한 노가다 끝에 나는 범인을 색출할 수 있었다. 처음에는 dotenv가 범인이지 않을까 싶었는데, 놀랍게도 진범은 따로 있었다.
해결
prisma가 문제였다. 이놈이 루트 디렉토리의 .env를 process.env에 덮어씌워버리는 것이다! 이 키워드를 가지고 구글에 검색해보니 이미 이 문제를 겪은 다른 개발자분의 블로그 포스트 Prisma는 process.env를 강제로 덮어씌운다 를 찾을 수 있었다. 라이브러리를 죽여버릴 수야 없겠지만, 꼬박 하루를 이 문제에 쏟아부은 탓에, 나도 모르게 prisma를 죽여버리고 싶다는 생각을 해버릴 정도였다.

해결 방법은 간단하다. 처음 찾았던 것처럼 루트 디렉토리에 .env를 쓰지 않으면 그만이다. 그런데 prisma는 루트 디렉토리에 .env가 없으면 제대로 동작하지 않는다. 따라서 dotenv-cli를 사용해 CLI 명령어 실행 전에 환경 변수를 로드해주어야 한다.
dotenv -e .env.local -- npx prisma generate더 읽어보기
2025.06.05
Next.js 데이터 캐시와 크롬 개발자 도구
상황 나는 page router를 좋아하지만, 이번에는 여러 사정이 있어서 app router를 쓰게 되었다. 이왕 쓰게 된 김에 이런 저런 기능을 몽땅 활용할 생각이었다. 가장 기대하던 기능은 데이터 캐싱이었는데, 가능한 많은 걸 캐싱해두면 랜더링 완료 시점을 한참 일찍 앞당길 수 있…
2025.05.21
14. Redis를 사용한 세션 관리 및 캐싱
새로운 프로젝트를 준비하면서 인증 방식부터 다시 고민하게 되었다. 이전 프로젝트에서는 JWT를 사용해 유저 인증과 상태 관리를 처리했지만, 이번에는 Redis를 활용한 세션 방식으로 전환하기로 결정했다. 이 방식은 유저 상태를 서버에서 직접 관리할 수 있어 보안 측면에서도 유리하고, 필…
2025.01.30
비즈니스 로직은 어디에 있어야 할까
Nest.js에서 컨트롤러와 각각의 프로바이더는 분명한 책임을 갖는다. 하지만 막상 서버를 개발하다 보면, 이 책임들을 깊이 고민하지 않은 채 코드를 작성하게 되고, 그 결과 클래스들이 서로의 영역을 침범하는 상황이 반복된다. 나 역시 컨트롤러에 비즈니스 로직이 섞이거나, 서비스가 지나…
2024.12.07
13. Jest 테스트 구성
이 포스트를 시작하기에 앞서 한 가지 분명히 밝혀두고 싶은 점이 있다. 나는 컨트롤러(혹은 GraphQL 기준으로는 리졸버) 와 레포지토리 클래스에 대한 테스트를 거의 작성하지 않는다. 이유는 단순하다. 애플리케이션에서 가장 많은 비즈니스 규칙이 응집되어 있는 곳은 서비스 계층이며, 테…
2026.06.01
React Server Components를 위한 컴포넌트 아키텍처
이 포스트는 Vercel의 Next.js 팀 소속 개발자 Aurora Scharff가 자신의 블로그에 올린 Component Architecture for React Server Components 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는…
2026.05.26
차트는 멈췄는데 윈도우가 움직인다
상황 어느날 서비스를 살펴보시던 팀장님께서 이런 말씀을 slack에 남기셨다. 진호님, 예측 차트에서 zoom을 계속하면 어느 순간 라인 차트가 아니라 단일 스캐터 차트처럼 보이는 데, 이거 수정하면 좋을 거 같아요. 어느정도 zoom을 하면 그 이후로는 zoom이 안 되도록 할 수 없…
2026.05.21
피자가게로 이해하는 디자인 패턴
에이든 피자는 처음부터 복잡한 시스템을 만들 생각이 없었다. 처음에는 메뉴 몇 개만 만들면 됐다. 그런데 손님은 커스텀 주문을 넣기 시작했고, 주방은 상태를 나눠야 했고, 결제와 배달앱과 알림이 하나씩 붙었다. 코드도 가게를 닮는다. 장사가 잘될수록 이상하게 더 쉽게 망가진다. 디자인…
2026.05.21
7. Decorator — 토핑 추가할 때마다 클래스를 새로 만들 수 없다
에이든 피자에서 주문서를 객체로 만들자 취소와 재주문은 한결 편해졌다. 그런데 주문이 편해지자 손님들도 한결 편해졌다. 편해진 손님은 더 많은 요구를 한다. "치즈 추가요", "올리브도 추가요", "소스 많이요", "조금 더 바삭하게 구워주세요" 같은 요청이 주문대 위로 쌓이기 시작했다…
댓글
댓글을 불러오는 중...