MSW 사용, 테스트 환경 세팅, TextEncoder is not defined 트러블 슈팅 기록

1️⃣ 왜 MSW를 도입했는가?

1. CORS 이슈

백엔드와 함께 협업하는 과정에서, CORS(Cross-Origin Resource Sharing) 문제를 만났습니다. 이를 해결하기 위해서 webpack의 Proxy를 사용하여 클라이언트 측에서 해결했습니다. 당시에는 CORS에 대한 지식이 부족해서, 이렇게만 하면 되는 줄 알았습니다.

// webpack.config.js
//...
proxy: [
    {
      context: ['/api'],
      target: process.env.REACT_APP_CRURU_API_URL,
      pathRewrite: { '^/api': '' },
      changeOrigin: true,
    },
  ],

2. 예기치 않은 API 요청 문제

이후 문제없이 개발하던 중, 어느 순간 API 요청이 먹통이 되는 상황이 발생했습니다. 처음에는 이것도 CORS 문제로 보였으나, 이미 해결했을 텐데 왜 오류가 뜨는지 의문이 들었습니다.

3. API 이슈와 MSW 도입의 필요성

알고 보니 백엔드 측에서 HTTP를 HTTPS로 변경하는 과정에서 설정이 잘못되어, 그 설정을 하는 동안 클라이언트 측의 프론트엔드 개발자들은 개발이 잠시 중단되는 상황이 발생했습니다. 추가적으로, 백엔드 측에서 HTTPS 허용 포트를 지정하면 클라이언트 측에서 프록시를 사용하지 않아도 된다는 것도 알게 되었습니다.

이러한 상황을 겪으면서, 만약 MSW(Mock Service Worker)를 설정해두었다면 막힘없이 개발을 계속 진행할 수 있었을 텐데라는 생각이 들었습니다. 그래서 MSW 세팅을 시작하게 되었습니다.

2️⃣ MSW 세팅 시작

1. 기본적인 세팅

MSW의 공식 문서에 따라 기본적인 세팅을 진행했습니다.

 

Mock Service Worker

API mocking library for browser and Node.js

mswjs.io

 

2. Jest 설정

테스트 세팅과 테스트 파일을 실행시키기 위해서 아래와 같은 코드를 작성했습니다.

//jest.config.js
module.exports = {
  transform: {
    '^.+\\\\.(t|j)sx?$': '@swc/jest',
  },
  testEnvironment: 'jsdom',
  testEnvironmentOptions: {
    customExportConditions: [''],
  },
  moduleNameMapper: {
    // 길어서 생략...
  },
  setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
  testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

여기서 저는 Jest의 트랜스파일러로 swc/jest를 사용했습니다. ts-jest와 다르게 타입 검사를 하지 않지만, 빠른 속도로 DX(Developer Experience)를 크게 향상시킬 것이라는 기대감으로 선택했습니다. 아래는 swc/jest, ts-jest, esbuild-jest를 비교한 이미지입니다.

esbuild-jest는 마지막 업데이트가 3년 전이고, 설정 시 정상적으로 작동하지 않는 모습을 보였습니다.

3. RTL 테스트 실행 중 에러

MSW의 공식 가이드를 따라서 RTL(React Testing Library) 테스트를 실행하게 되면 다음과 같은 에러가 나타났습니다.

ReferenceError: TextEncoder is not defined

3️⃣ TextEncoder is not defined 문제 발생 원인

  1. RTL 컴포넌트 테스트는 기본적으로 jest-dom 환경에서 실행되기 때문에 브라우저 환경으로 생각하면 됩니다.
  2. 그런데 MSW의 Server는 node 환경에서 구동됩니다.
  3. TextEncoder는 Node.js의 API 중 하나이기 때문에, jsdom 환경에서는 실행되지 않습니다. (아래 Node.js 공식 문서를 참고하세요.)
  4. 그렇다고 jest.config.js의 testEnvironment: 'jsdom' 부분을 node로 변경하면, RTL이 구동되지 않습니다.
  5. 따라서 이 문제를 해결하기 위해서는 node와 jsdom의 환경을 모두 지원해야 합니다.
 

Util | Node.js v22.5.1 Documentation

Util# Source Code: lib/util.js The node:util module supports the needs of Node.js internal APIs. Many of the utilities are useful for application and module developers as well. To access it: const util = require('node:util'); copy util.callbackify(original

nodejs.org

 

해결 방법 1: 수동 설정

  1. node에 존재하는 API를 수동으로 jsdom 환경에 옮겨주는 방법입니다. 아래 코드를 가진 파일을 하나 작성합니다.
npm i -D undici
// jest/polyfills.js
const { TextDecoder, TextEncoder } = require("node:util");
const { ReadableStream } = require("node:stream/web");

Object.defineProperties(globalThis, {
  TextDecoder: { value: TextDecoder },
  TextEncoder: { value: TextEncoder },
  ReadableStream: { value: ReadableStream },
});

const { Blob, File } = require("node:buffer");
const { fetch, Headers, FormData, Request, Response } = require("undici");

Object.defineProperties(globalThis, {
  fetch: { value: fetch, writable: true },
  Blob: { value: Blob },
  File: { value: File },
  Headers: { value: Headers },
  FormData: { value: FormData },
  Request: { value: Request },
  Response: { value: Response },
});
//jest.config.js
//...
setupFiles: ['./jest.polyfills.js'], // 이 코드를 추가합니다.

해결 방법 2: jest-fixed-jsdom 사용

msw 공식에서 만든 라이브러리 jest-fixed-jsdom을 사용하는 방법입니다. 가장 간단한 방법입니다.

 

GitHub - mswjs/jest-fixed-jsdom: A superset of the JSDOM environment for Jest that respects Node.js globals.

A superset of the JSDOM environment for Jest that respects Node.js globals. - GitHub - mswjs/jest-fixed-jsdom: A superset of the JSDOM environment for Jest that respects Node.js globals.

github.com

npm i jest-fixed-jsdom --save-dev
// jest.config.js
module.exports = {
  testEnvironment: 'jest-fixed-jsdom',
}

위의 방법을 통해 TextEncoder is not defined 문제를 해결하고, MSW를 성공적으로 세팅하여 API이슈에도 지속적으로 개발 가능한 환경을 구축할 수 있었습니다.