Next.js에 테스트 도입하기(feat. jest)

Jan 17, 2025

23 views

Next.js에서 Jest 도입하기

본격적인 내용을 작성하기에 앞서 해당 글은 Next.js와 TypeScript 환경 기준으로 작성되었으며 버전은 작성 시점 현재 최신 버전으로 적용했다.

"dependencies": {
    "next": "15.1.4",
    "react": "19.0.0",
    "react-dom": "^19.0.0"
},
"devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "@types/jest": "^29.5.14",
    "jest": "^29.7.0",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
}

테스트 환경 설정을 구축하기 쉽지 않은 이유

1. ESM과 CJS 모듈 시스템의 차이

  • Next.js는 기본적으로 ESM(ECMAScript module, import/export)을 사용하는데 반하여 Jest는 CJS(CommonJS, require, module.exports) 모듈을 적용하고 있다.
  • ESM을 CJS로 방식으로 변환해주는 트랜스파일러인 Babel을 통해서 해당 문제를 해결할 수 있다.
# 본인이 사용중인 패키지 매니져에 맞게 의존성 설치
npm i -D @babel/preset-env @babel/preset-react @babel/preset-typescript
# 또는
yarn add -D @babel/preset-env @babel/preset-react @babel/preset-typescript
# 또는
pnpm add -D @babel/preset-env @babel/preset-react @babel/preset-typescript
  • 해당 의존성 설치 이후에 프로젝트 루트 디렉토리에 'babel.config.js'(또는 .babelrc) 생성 후에 다음과 같이 작성한다.
module.exports = {
  presets: [
    '@babel/preset-env', // ES6+ -> ES5
    '@babel/preset-react', // JSX -> JavaScript
    '@babel/preset-typescript', // TypeScript -> JavaScript
  ],
}

2. TypeScript 환경에서 Babel 적용 문제

  • Next.js는 TS를 지원하지만 Jest는 기본적으로 TS를 지원하지 않기 때문에 별도의 의존성이 필요하다.
npm i -D ts-jest ts-node babel-jest
  • 설치 이후에 마찬가지로 프로젝트 루트 디렉토리에 'jest.config.ts', 'jest.setup.js' 파일을 각각 생성 후 다음과 같이 수정한다.
// /jest.config.ts
import { Config } from 'jest'

const config: Config = {
  // ts-jest로 TS 파일 처리
  preset: 'ts-jest',
  // Jest 테스트 환경 설정
  testEnvironment: 'jest-environment-jsdom',
  // TS 파일을 ts-jest를 통해서 반환
  transform: {
    '^.+\\.(ts|tsx)$': [
      'ts-jest',
      // 테스트 환경에서 별도의 tsconfig.test.json을 사용하고 Babel 설정도 포함
      { tsconfig: '<rootDir>/tsconfig.test.json', babelConfig: true },
    ],
    // JS 파일을 babel-jest를 통해 반환
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
  // 모듈을 찾는 경로 설정
  moduleDirectories: ['node_modules', '<rootDir>/'],
  // 테스트 실행전에 실행할 파일 설정
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // 테스트를 제외하는 경로
  testPathIgnorePatterns: ['/node_modules/', '/.next/'],
  // Jest가 transform을 제외할 파일 패턴 설정
  transformIgnorePatterns: ['/node_modules/(?!@testing-library/.*)'],
  // 경로 매핑 설정: '@/' -> '/src/'
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
}

export default config
// jest.setup.js
import '@testing-library/jest-dom'
  • 옵션들을 전부 다 숙지할 필요는 없지만 당장은 각 옵션들이 어떤 역할을 하는지 정도만 간단히 이해하고 넘어가도 좋다.
  • 지금까지 설정한 내용들을 TypeScript 환경에 적용하기 위해서는 마지막으로 'tsconfig.json'을 수정해야 한다.
  • 'tsconfig.json' 단일 파일에 모든 설정을 적용해도 무방하지만 Next.js와 Jest를 위한 TS 설정이 약간의 차이가 있기 때문에 Jest만을 위한 환경 설정 파일을 따로 관리하는 방법을 적어보겠다.
  • 루트 디렉토리에 'tsconfig.test.json'을 생성하고 다음과 같이 수정한다.
// tsconfig.test.json
{
  // tsconfig.json 파일에서 확장
  "extends": "./tsconfig.json",
  // /src/__tests__ 하위의 테스트 코드만 테스트에 포함
  "include": ["src/__tests__/**/*.ts", "src/__tests__/**/*.tsx"],
  // 테스트를 제외하는 경로
  "exclude": ["node_modules", ".next"]
}
// tsconfig.json
{
    "compilerOptions: {
        // 생략
        // Node.js 전역 타입 정의, Jest matcher 타입 정의
        "types": ["node", "@testing-library/jest-dom"]
    }
}
  • 만약 Next.js에서 SSR이 아닌 CSR만 다룬다면 node 타입 정의는 생략해도 무방(그럴 일이 없겠지만)

tsconfig.json 설정 적용하면서 헷갈렸던 점

  • "types": ["@testing-library/jest-dom"] 설정을 'tsconfig.test.json'이 아닌 'tsconfig.json'에서 설정해줘야 하는 이유는 Jest 테스트 전용 타입 정의가 전역으로 설정되어야만 컴파일 단계에서 인식이 가능하기 때문이다.

이렇게 설정하면 Next.js에서 테스트 코드를 작성하고 테스트 가능한 환경이 구축된다.

테스트 코드 작성 예시

테스트 코드 작성 전 jest를 실행하기 위한 커맨드를 추가한다.

{
  "scripts": {
    "test": "jest"
  }
}

테스트를 위한 예시 컴포넌트

// /src/app/page.tsx
export default function Home() {
  return (
    <div>
      <h1 className="typo-display text-brand-primary">myno</h1>
    </div>
  )
}
  • /src/__tests__/app 디렉토리에 'page.test.tsx'를 생성하고 다음과 같이 수정한다.
// 테스트 코드 작성 예시

import { render, screen } from '@testing-library/react'
import Home from '@/app/page'

describe('Home 컴포넌트 렌더링 테스트', () => {
  test("화면에 'myno' 문자열이 렌더링되어야 한다.", () => {
    render(<Home />)
    expect(screen.getByText(/myno/i)).toBeInTheDocument()
  })
})
npm run test
test-result

test 커맨드 실행 후 터미널에서 테스트 결과를 확인해보자..!