개발하는 일상

모노레포에서 Next.js 앱 테스트 환경 통합하기: Jest 설정 패키지 만들기 본문

개발 기록

모노레포에서 Next.js 앱 테스트 환경 통합하기: Jest 설정 패키지 만들기

롯데빙빙바 2024. 12. 15. 23:53

코드를 먼저 보고 싶다면: https://github.com/bingbing-ba/turborepo-next-jest-config-package

 

GitHub - bingbing-ba/turborepo-next-jest-config-package

Contribute to bingbing-ba/turborepo-next-jest-config-package development by creating an account on GitHub.

github.com

 

 

우리 팀은 현재 3개의 Next.js 앱을 모노레포(monorepo)로 관리하고 있다. 이번에 테스트 코드를 활용할 일이 생겨 처음으로 테스트 환경을 구축하게 되었다. 각 앱별로 개별 설정을 관리하기보다, 모노레포 구조를 활용해 공통 설정 패키지를 만들어보기로 했다. 사실 앱 개수가 그렇게 많지 않아 반드시 공통 패키지로 만들 필요는 없었지만, 아주 약간의 욕심을 부려보았다. 혹 서비스마다 다른 환경이 필요해진다면 나중에 분리하기로 생각했다.

 

이 글에서는 우리 팀의 실제 코드가 아닌 turborepo의 스타터 템플릿에 기반하여 설명하려고 한다. 쉬운 코드를 예시로 보여줄 수 있으며, 설정쪽 코드 자체는 크게 다르지도 않다. Turborepo의 스타터 템플릿을 기반으로 jest 설정 패키지를 추가한 버전을 github에 올려두었다.

 

설정은 Next.js 공식 문서의 Jest 설정 가이드를 참고했으며, monorepo와 Turborepo 환경에 맞게 수정한 부분을 중심으로 설명하겠다.

폴더 구조 설명

- apps/docs  - Next.js 앱 (패키지)
- apps/web   - Next.js 앱 (패키지)
- packages/eslint-config     - ESLint 설정 패키지
- packages/typescript-config - TypeScript 설정 패키지
- packages/ui                - UI 패키지
- packages/next-jest-config  - Jest 설정 패키지 (새로 추가됨)

여기서 packages/next-jest-config는 내가 새로 추가한 부분이고, 나머지 구조는 Turborepo 스타터 템플릿에서 제공되는 기본 구성이다. Turborepo 초기 설정 방법이 궁금하다면 공식 문서를 참고하길 바란다.

참고로, 템플릿에 포함된 ESLint와 TypeScript 설정 패키지에서 아이디어를 얻어 Jest 설정 패키지를 구성했다.

 

Jest Config 패키지 만들기

1. packages/next-jest-config 폴더를 만들고, 그 곳에서 package.json을 만든다. 다른 패키지에서 사용할 수 있도록 적절한 이름을 설정하고, jest를 실행할 수 있게 test 커맨드를 설정한다.

{
  // 나중에 nextjs app에서 패키지에 포함할 때 쓸 이름이다
  "name": "@repo/next-jest-config",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
   ...

2. Next.js 공식 문서에 따라 필요한 의존성을 설치한다:

npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node

주의: 문서에는 언급되지 않은 @types/jest 패키지를 설치해야 한다. TypeScript로 테스트를 작성하려면 필수다.

 

Jest 설정 파일 작성

처음에는 npm init jest@latest 명령어로 기본 템플릿을 생성했다.

Next.js 공식문서에 나오는대로 설정을 따라가면 되는데, monorepo의 package이기 때문에 조금 다르게 설정해야하는 부분이 있다. 

 

1. 모노레포 환경에서는 Next.js 앱의 경로를 각 앱에 맞게 조정해야 한다. 그래서 이를 환경 변수로 처리하기로 했다.

// packages/next-jest-config/jest.config.ts
const createJestConfig = nextJest({
  dir: process.env.NEXT_APP_PATH,
});

...

 

2. Next.js 앱에서 Path Alias를 사용하는 경우, 이를 Jest에서도 인식할 수 있도록 설정이 필요하다. 각 앱의 tsconfig.json에서 Path Alias를 읽어와 처리하는 코드를 작성했다. Github에 올려둔 예시코드에서는 path alias가 없지만, 실제 서비스에서는 많이 사용하는 부분이라서 남겨두었다.

// packages/next-jest-config/jest.config.ts

...

// 2. path alias 설정
const getTsConfigPath = () => {
  const tsConfig = require(process.env.NEXT_APP_PATH + '/tsconfig.json');
  const { paths } = tsConfig.compilerOptions as { paths: Record<string, string[]> };

  const moduleNameMapper = {} as Record<string, string[]>;

  if (!paths) {
    return {};
  }

  Object.keys(paths).forEach((path) => {
    const key = path.replace('/*', '/(.*)');
    const value = (paths[path] || []).map((p) => `<rootDir>/${p.replace('/*', '/$1')}`);
    moduleNameMapper[`^${key}$`] = value;
  });

  return moduleNameMapper;
};

const config: Config = {
  // 2. path alias를 위한 설정
  moduleNameMapper: { ...getTsConfigPath() },

  // test 환경의 rootDir, next 앱의 경로이다
  rootDir: process.env.NEXT_APP_PATH,

  // 나는 주로 서버 환경에서 실행될 테스트가 많아서 node로 설정했다.
  testEnvironment: 'node',

  // The glob patterns Jest uses to detect test files
  testMatch: ['<rootDir>/**/?(*.)+(spec|test).[tj]s?(x)'],
};

const nextJestConfig = createJestConfig(config) as () => Promise<Config>;

export default nextJestConfig;

3. jest config를 ts 파일로 작성했으니, tsconfig.json 파일도 필요하다. 템플릿에서 제공하는 ts config가 있어서 수월하게 작성하였다.

{
  "extends": "@repo/typescript-config/base.json",
  "include": [
    "**/*.ts",
  ],
  "exclude": ["node_modules"]
}

테스트 코드 실행을 위한 설정

설정 만들기를 완료했으니 Next.js 앱에 설정할 차례이다. apps/docs의 package.json에 만든 package를 추가한다.

// apps/docs/package.json

...
  "devDependencies": {
	...
    "@repo/next-jest-config": "*"
 ...

 

 

모노레포이니 루트에서 실행할 수 있도록 turbo.json과 package.json을 수정한다.

// turbo.json

{
  ...
  "tasks": {
    ...
    "test": {
      "dependsOn": ["^test"]
    }
  }
}

간단히 설명하면, test라는 태스크는 모든 디펜던시의 test 커맨드를 바닥부터 찾아 실행해나간다. 우리의 경우는 docs 패키지 아래에 @repo/next-jest-config 에만 test 커맨드가 있으므로, 그 커맨드를 실행하고나면 태스크는 끝난다. 자세한 설명은 turbo의 공식문서를 참고하길 바란다.

 

// package.json

{
  "name": "turborepo",
  "private": true,
  "scripts": {
	...
    "test:docs": "NEXT_APP_PATH='../../apps/docs' turbo run test --filter=docs --"
  },
...

루트 package.json에 test:docs 커맨드를 추가했다. 커맨드에서는 테스트 환경이 실행될 경로를 환경변수로 입력한다. 위에서 말한 것 처럼 결국 디펜던시를 뒤져 test 커맨드가 실행되는 곳은 pacakges/next-jest-config 이므로, 해당 경로를 기준으로 Next.js 앱이 있는 경로를 환경변수로 입력하면 된다.

중간에 보이는 --filter=docs는 docs 앱에서만 test 태스크를 실행하라는 의미이다. 

마지막 --는 혹시 특정 경로의 테스트파일만을 실행하고자 할 때를 대비해 넣어두었다. 아래처럼 사용 가능하다.

# docs 앱의 모든 테스트를 실행
npm run test:docs

# 특정 테스트 파일만 실행
npm run test:docs apps/docs/app/actions/sampleAction.spec.ts

 

 

이렇게 설정해두면 다른 Next.js 앱에서 테스트 환경을 구축하고자 할 때 package.json파일만 조금 수정해주면 곧바로 사용할 수 있게 된다.

 
Comments