[jest & msw] jest + msw 적용 안되는 문제 해결

Gyuhan Park·2024년 7월 20일
3

Trouble Shooting

목록 보기
7/10

📘 TextEncoder is not defined

jest로 api 요청하는 컴포넌트를 테스트 돌려보면 TextEncoder is not defined 라는 오류가 발생한다.

공식문서에 따르면 jest에서 Node.js globals를 빼앗고 다시 추가하지 않기 때문에 오류가 발생한다고 한다. 그래서 이를 사용자가 직접 명시적으로 추가해야 한다.

📘 Cannot find module ‘undici’ from ‘jest.polyfill.js’

공식문서에 작성된 jest.polyfill.js 를 그대로 가져오고 jest.config.json에 setupFiles 파일로 설정한다.

설정 후 테스트 코드를 돌려보면 undici 모듈이 없다고 뜬다 ㅋㅋㅋㅋ

jest.polyfills.js

/**
 * @note The block below contains polyfills for Node.js globals
 * required for Jest to function when running JSDOM tests.
 * These HAVE to be require's and HAVE to be in this exact
 * order, since "undici" depends on the "TextEncoder" global API.
 *
 * Consider migrating to a more modern test runner if
 * you don't want to deal with this.
 */

const { TextDecoder, TextEncoder } = require('node:util');

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

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.json

{
  "testEnvironment": "jsdom",
  "moduleNameMapper": {
    "^@/(.*)$": "<rootDir>/src/$1"
  },
  "setupFiles": ["./jest.polyfills.js"],
  "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
}

📘 ReadableStream is not defined

그래서 undici 를 설치하고 다시 실행시켜보면 ReadableStream is not defined 라는 오류가 발생한다. polyfill을 읽어보면 ReadableStream을 global에 정의하고 있지 않다. 그래서 추가를 해봤는데도 적용되지 않고 라이브러리 내에도 정의되어 있지 않았다. 해당 문제는 undici가 현재 6버전인데 5버전으로 다운그레이드 시켜 설치하면 해결되었다.

npm i -D undici@5

또는 6버전에서 jest.polyfill.js 를 수정하여 사용할 수도 있다.

// jest.polyfills.js
/**
 * @note The block below contains polyfills for Node.js globals
 * required for Jest to function when running JSDOM tests.
 * These HAVE to be require's and HAVE to be in this exact
 * order, since "undici" depends on the "TextEncoder" global API.
 *
 * Consider migrating to a more modern test runner if
 * you don't want to deal with this.
 */

const { TextDecoder, TextEncoder } = require('node:util');
const { ReadableStream, TransformStream } = require('node:stream/web');

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

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 },
});

📘 Cannot find module ‘msw/node’

이렇게 문제를 해결하고 났더니 msw/node 모듈을 찾지 못한다고 한다 ㅋㅋㅋㅋ 이 또한 공식문서에 설명되어 있어서 추가로 설정하였다. 공식문서에 따르면 testEnvironmentOptions 을 설정해주면 된다.

jest.config.json

{
  "testEnvironment": "jsdom",
  "testEnvironmentOptions": {
    "customExportConditions": [""]
  },
  "moduleNameMapper": {
    "^@/(.*)$": "<rootDir>/src/$1"
  },
  "setupFiles": ["./jest.polyfills.js"],
  "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
}

🚨 해당 오류가 발생하는 이유

오류가 나는 이유를 좀더 자세히 살펴보면 JSDOM과 msw/node 와의 환경 차이 때문에 발생한다. document is not defined 에러로 JSDOM은 실제 브라우저 환경에서 테스트를 돌린다는 것을 알 수 있다. 그래서 우리는 hook을 테스트하거나 컴포넌트를 테스트하기 위해 testEnvironment 를 jsdom으로 설정했었다.

그런데, JSDOM으로 테스트를 실행하면 Node.js 내에서 브라우저 환경을 시뮬레이션하려고 시도한다. 그래서 브라우저 기준으로 모듈 경로를 확인하는데, MSW와 같은 third-party 패키지를 가져올 때 JSDOM은 브라우저 내보내기를 진입점으로 사용하도록 강제한다.

해당 문제를 해결하기 위해 msw/mode 를 가져올 때 default export 조건 을 사용하도록 하여 문제를 해결할 수 있다.

{
  "testEnvironmentOptions": {
    "customExportConditions": [""]
  },
  ...
}

실제 msw 패키지를 열어보면 exports 방식에 따라 사용되는 코드가 다르다. 이 때문에 JSDOM 환경에서 broswer로 테스트를 돌리면 msw/node 코드를 찾을 수 없다는 오류가 발생하는 것이다.

node_modules/msw/package.json

{
  "name": "msw",
  "version": "2.3.1",
  "description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
  "main": "./lib/core/index.js",
  "module": "./lib/core/index.mjs",
  "types": "./lib/core/index.d.ts",
  "packageManager": "pnpm@8.15.6",
  "exports": {
    ".": {
      "types": "./lib/core/index.d.ts",
      "require": "./lib/core/index.js",
      "import": "./lib/core/index.mjs",
      "default": "./lib/core/index.js"
    },
    "./browser": {
      "types": "./lib/browser/index.d.ts",
      "browser": {
        "require": "./lib/browser/index.js",
        "import": "./lib/browser/index.mjs"
      },
      "node": null,
      "require": "./lib/browser/index.js",
      "import": "./lib/browser/index.mjs",
      "default": "./lib/browser/index.js"
    },
    "./node": {
      "types": "./lib/node/index.d.ts",
      "node": {
        "require": "./lib/node/index.js",
        "import": "./lib/node/index.mjs"
      },
      "browser": null,
      "require": "./lib/node/index.js",
      "import": "./lib/node/index.mjs",
      "default": "./lib/node/index.mjs"
    },
		...
  },
}

✅ 해결

모든 이슈를 해결하면 테스트 코드가 정상적으로 동작하는 것을 볼 수 있다.

useQuestionQuery.test.tsx

import { renderHook, waitFor } from '@testing-library/react';

import useQuestionQuery from '@/hooks/useQuestionQuery';
import wrapper from '@/mocks/wrapper';

test('useQuestionQuery가 mock server에서 데이터를 가져온다.', async () => {
  const { result } = renderHook(() => useQuestionQuery(), { wrapper });

  await waitFor(() => expect(result.current.isSuccess).toBe(true));
});

https://mswjs.io/docs/migrations/1.x-to-2.x#cannot-find-module-mswnode-jsdom

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글