testing-library 적용 및 Jest 성능 개선기 (1)

최준만·2025년 4월 28일
post-thumbnail

개요

사용 목적

이 문서는 Jest를 활용하여 TypeScript 코드를 테스트하는 방법과 테스트 환경 설정에 관한 내용을 다룹니다.
testing-library/dom을 적용하여 DOM 조작을 테스트하는 환경을 설정하고, Jest 성능 개선 방법을 다룹니다.

테스트의 목적

  • 개발 과정에서 발생할수 있는 버그와 엣지 케이스에 대한 검증을 사전에 할 수 있음
  • 코드 품질에 대한 지속적 관리가 가능함

테스트 코드 작성의 이점

  • 리팩토링 및 유지보수 비용 감소
    - 특정 코드를 수정할 때 테스트 통과 유무에 따라 사이드이펙트 발생에 대한 사전 조치가 가능합니다.
  • 기술 명세로서 활용
    - 코드에 대한 input/output을 검증하는 코드를 통해 기술 문서 없이도 어떠한 역할과 책임을 가지는지 파악이 가능합니다.
  • 로직에 대한 검증과 신뢰도 확보
    - 작성되어있는 테스트의 품질에 따라 코드의 신뢰도를 확보하고 디버깅 시에 관찰 우선순위를 정하는 데에 있어 용이합니다.

제약 사항

기술적 제약(혹은 조건)

  • Rune 프레임워크에 대응해 동작 해야합니다.
  • dom 을 렌더하고 테스트 할 수 있어야 합니다.

해결책(Ideation)

Rune 의 컴포넌트 클래스는 render 메서드를 기본적으로 가지고 있습니다.
render 메서드는 최종적으로 HTMLElement를 리턴하기 때문에 이를 DOMTree 로 구성하고 테스트 할 수 있는 라이브러리인 @testing-library/dom을 적용 했습니다.

testing-library란?

testing-libraryReactDOM 요소의 사용자 상호작용을 기반으로 테스트를 작성할 수 있는 라이브러리입니다.
이를 통해 UI 테스트사용자 관점으로 진행할 수 있습니다.

구현

ts-jest 를 적용해 typescript 코드 테스트

ts-jest 란?

ts-jestJestTypeScript를 통합하기 위한 도구로, TypeScript 파일을 Jest가 테스트할 수 있도록 변환합니다.

ts-jest 설정

  • 설치
npm install --save-dev jest ts-jest @types/jest
  • jest.config.cjs
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

기본설정법은 위와 같습니다.

다만, js는 기본 babel-jest 를 사용하고 ts 파일에 대해서만 ts-jest 를 사용하고 싶었기에 아래와 같이 설정했습니다.

  • jest.config.cjs
module.exports
	preset: 'ts-jest',  
	testEnvironment: 'node',
	moduleFileExtensions: ['js', 'mjs', 'cjs', 'json', 'ts'],
	transform: {  
	  '^.+\\.(ts|tsx)$': 'ts-jest',  
	  '^.+\\.(js|mjs)$': 'babel-jest',  
	},
	...
}

testing-library/dom 적용

패키지 설치

testing-library/dom 을 사용하기 위해 3가지 모듈을 설치 했습니다.

//package.json
"jest-environment-jsdom": "^29.7.0",
"@testing-library/dom": "^10.4.0",  
"@testing-library/jest-dom": "^6.6.3",
  • jest-environment-jsdom : jsdom 기반으로 가상의 DOM 환경을 구성해주는 Jest 환경 설정
    - 원래 기본으로 jest에 내장되어 있었으나 27버전부터 분리되어 직접 설치해 주었습니다.
  • @testing-library/jest-dom : Jest 전용 확장 Matcher 제공
    - expect(...).toBeInTheDocument()
    - expect(...).toHaveClass(...)
    - expect(...).toBeVisible()
  • @testing-library/dom : jsdom(또는 브라우저) 환경에서 HTML 요소에 접근하고 조작하는 라이브러리
    - screen, getByText, fireEvent 등 DOM을 조작하고 추출할 수 있는 함수들을 가지고 있습니다.

참고 : jsdom 이란?

  • jsdom은 Node.js에서 HTML을 파싱하고 DOM처럼 동작하게 해주는 오픈소스 라이브러리입니다.
  • Jest에서 가상의 브라우저 환경을 만들어서 테스트하기 위해 사용됩니다.

jest 설정

  • jest.config.cjs
// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',          // jsdom 환경 설정
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], // 전역 설정 파일 (선택)
  moduleFileExtensions: ['ts', 'tsx', 'js'],
  testMatch: ['**/__tests__/**/*.test.(ts|tsx|js)'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
};

풀스택 레포지토리에서의 테스트 환경 분리

문제상황 : 기존 단위 테스트들은 Node 환경에서 테스트 되어 왔습니다.
하지만 jsdom 환경으로 jest를 실행시켜야만 dom에 대한 테스트가 가능해 집니다.
이를 어떻게 해결해야 할까요?

jest config 중 project라는 설정이 있습니다.

module.exports = {  
  maxWorkers: 1,  
  projects: [  
    {  
      displayName: 'node',  
      testEnvironment: 'node',  
      testMatch: [  
        '<rootDir>/modules/**/*.spec.(js|cjs|mjs|ts)',  
        '<rootDir>/services/**/*.spec.(js|cjs|mjs|ts)',  
      ],  
      testPathIgnorePatterns: ['\\.dom\\.spec\\.ts$'], //dom 테스트 제외  
      moduleFileExtensions: ['js', 'mjs', 'cjs', 'json', 'ts'], 
      ...
    },  
    {  
      displayName: 'jsdom',  
      testEnvironment: 'jsdom',  
      testMatch: ['<rootDir>/services/**/*.dom.spec.(ts|js)'],  
      moduleFileExtensions: ['js', 'mjs', 'cjs', 'json', 'ts'],       ...
    },  
  ],  
};
  • 각 환경을 분리하고, dom 테스트 파일에 대한 식별자를 **.dom.* 형태로 규정지었습니다.

글로벌 인스턴스 문제 해결

문제상황 : 두근 거리는 마음으로 테스트를 돌리려는 찰나에 글로벌 i18n 인스턴스가 undefined 가 되어 출력되는 이슈가 발생 했습니다!

setupAfterEnvFile

  • Jest가 테스트를 실행하기 전에 공통 설정을 자동으로 실행하도록 지정하는 옵션입니다.

실제로 i18n 인스턴스를 글로벌 객체에 넣어서 쓰고 있는 구조이므로, 주입이 필요한 상황입니다.
이를 해결하기 위해 setup 파일을 구성하고 글로벌 객체에 필요한 값을 주입해 주는 코드를 작성하고, 이를 Jest가 공통적으로 실행하도록 설정하여 해결했습니다.

// setup.ts
import i18next from 'i18next';
  
type I18nInstance = {  
  (lng?: string): Promise<(key: string, options?: any) => string>;  
  lang: string;  
};  
  
let isInit = false;  
// I18N INIT 셋업  
(async function initTranslator() {  
  if (isInit) return;  
  
  const lang = process.env.LANG || 'kr';  
  const t = await i18next.init({  
    lng: lang,  
    fallbackLng: 'kr',  
    resources: {},  
  });  
  isInit = true;  
  
  global.T = t;
  
// jest.config.cjs
module.exports = {
  setupFilesAfterEnv: ['<rootDir>/jest/setup.ts'],
};

예고편) 이슈 : 힙 메모리 폭발!

out of js heap memory

  • 대체 무엇이,,, 힙메모리를 2GB나 쓰는 것인가?
  • github action의 메모리 스펙과 힙메모리의 연관성은 어떻게 되는 것일까?

와 더불어 jest 성능 개선 방법을 다음 시간에 알아보겠습니다.

profile
junman9595@gmail.com

1개의 댓글

comment-user-thumbnail
2025년 4월 29일

와 잘봤습니다!!
힙메모리 이슈 너무 궁금하네요🧐

답글 달기