모노레포의 패키지에 Jest 환경 설정하기

회사에서 만들고 있는 패키지 모노레포에 Jest를 이용하여 테스트 코드를 작성해보았습니다.

6 min read
views

개요

Turbo를 이용해서 패키지들을 관리하는 모노레포를 회사에서 구축하고 있다. 그 중 util로 자주 사용하는, 이를테면 배열의 마지막 요소인지 판별하는 isLastIndex와 같이 util 느낌의 로직들을 모아놓은 패키지에 clamp라는 함수를 추가하다가 테스트 코드를 작성해야겠다는 생각이 들었다. clamp라는 함수는 역할이 자명하여 변경될 여지가 매우 적지만, 다른 로직들도 마찬가지고 향후 혹여나 로직이 잘못 변경될 여지를 줄이기 위해서였다.

// isLastIndex.ts, 배열의 마지막 요소인지 판단하는 함수
export function isLastIndex<T>(index: number, array: T[]) {
  return Array.isArray(array) && index === array.length - 1;
}
 
// clamp.ts, 최솟값과 최댓값을 넘지 않도록 값을 제한하는 함수
export function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max);
}

방법

A. 설치

다음 패키지들을 설치하자. (해당 모노레포에서는 pnpm을 사용하고 있어 pnpm으로 작성)

pnpm add --save-dev jest @types/jest ts-jest babel-jest @bable/core @bable/preset-env @babel/preset-typescript

B. 명령어 추가

그리고 명령어로 테스트를 실행시킬 수 있도록 package.json의 scripts에 test 명령어를 추가한다.

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

C. Babel 설정

설치한 패키지들에서 유추할 수 있듯이, jest는 babel을 통해서 typescript를 지원한다. 여기서 babel이란 javascript 컴파일러로, ECMAScript 2015 이상 환경에서 사용할 수 있는 코드들을, 이전 환경에서도 동작할 수 있도록 해준다. babel을 이용해서 typescript 또한 javascript 변환시킬 수 있다.

다음은 babel을 통해서 어떻게 코드가 변환되는지 보여주는 예시이다.

// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);
 
// Babel Output: ES5 equivalent
[1, 2, 3].map(function (n) {
  return n + 1;
});

babel.config.js를 root directory 경로에 만들고 다음과 같은 코드를 입력한다.

// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
};

D. Jest 설정

향후 jest 설정도 커스텀하기 위해, root directory 경로에 jest.config.js 파일을 만들고 다음과 같이 간단히 작성해준다.

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/*.spec.[jt]s?(x)', '**/*.test.[jt]s?(x)'],
};

E. 테스트 코드 작성

// clamp.spec.ts
import { clamp } from '.';
 
describe('clamp 함수 테스트', () => {
  test('value가 min보다 작을 때 min을 반환한다.', () => {
    expect(clamp(-1, 0, 10)).toBe(0);
  });
 
  test('value가 max보다 클 때 max를 반환한다.', () => {
    expect(clamp(11, 0, 10)).toBe(10);
  });
 
  test('value가 min과 max 사이에 있을 때 value를 반환한다.', () => {
    expect(clamp(5, 0, 10)).toBe(5);
  });
 
  test('value가 min과 같을 때 value(or min)를 반환한다.', () => {
    expect(clamp(0, 0, 10)).toBe(0);
  });
 
  test('value가 max와 같을 때 value(or max)를 반환한다.', () => {
    expect(clamp(10, 0, 10)).toBe(10);
  });
 
  test('value가 min과 max가 같을 때 value(or min or max)를 반환한다.', () => {
    expect(clamp(5, 5, 5)).toBe(5);
  });
});

추가. Turbo 설정

모노레포의 root directory에서 turbo test라는 명령어 입력만으로도 패키지들에 작성되어 있는 테스트 코드들이 실행되도록 turbo.json에 다음과 같이 코드를 추가하였다.

"pipeline": {
  "test": {
    "dependsOn": ["^build"]
  }
}

위에서 dependsOn은 해당 태스크가 실행되기 위해 이전에 완료되어야 하는 작업을 말한다. 위와 같은 경우, turbo test는 turbo build가 완료되어야 실행된다.

Continuous Integration

Pull Request가 merge될 때 테스트 코드를 동작 시키기 위해서 Github Actions를 사용해보았다. 모노레포의 root directory에서 .github/workflows 폴더를 만들고 ci.yml 파일을 만든 후 다음과 같이 작성해주자.

name: Continuous Integration
 
on:
  push:
    branches: ['main']
  pull_request:
    types: [opened, synchronize]
 
jobs:
  build:
    name: Build and Test
    timeout-minutes: 15
    runs-on: ubuntu-latest
 
    steps:
      - name: Check out code
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
 
      - uses: pnpm/action-setup@v2
        with:
          version: 8
 
      - name: Setup Node.js environment
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
 
      - name: Install dependencies
        run: pnpm install
 
      - name: Build
        run: pnpm build
 
      - name: Test
        run: pnpm test

레퍼런스