Webpack,jest에서 MSW 적용하기

badahertz52·2024년 9월 9일
3

💫우테코_6기

목록 보기
12/17
post-thumbnail

MSW가 필요한 이유

우테코 레벨3에 팀 프로젝트를 하면서 Webpack에서 MSW를 설치해, 프로덕션 코드와 테스트 코드에 사용하는 Mock Server(이하:목서버)를 만들었다.

백엔드 API가 준비되기 전이나 서버가 수정 중 또는 문제가 있었을 때에도 프론트가 개발을 하기 위해서 목서버가 필요했다.
그리고 HTTP 요청에 대한 테스트시, 직접 서버에 요청을 보내는 것이 아닌 목서버로 요청을 보내 테스트를 실행해야했다.

MSW 적용하기

Webpack 설정 및 핸들러

browser vs server

MSW는 사용환경에 따라 browser,node.js(server), React Navie로 나뉜다.

리액트, 스토리 북에서 사용할 거라면 브라우저 환경에서 네트워크 요청을 가로채 모킹하는 browser 설정을 해야한다.

테스트처럼 브라우저 없이 서버 환경에서 네트워크 요청을 모킹하려면 Node.js(server)에 대한 설정을 해야한다.

🤯 'msw/browser' is not defined by "exports" 오류

ESLint: Unable to resolve path to module 'msw/browser'.(import/no-unresolved)

msw/browser에 대해 eslint 오류가 났다. 오류가 날 수 있는 여러 가능성(패키지 설치 안됨, 경로가 잘못됨 등)을 확인해봤지만 문제는 없었다. MSW를 적용한 다른 크루들과의msw/browser 사용법도 다르지 않았다.
그래서 eslint 적용을 하지않도록 하는 주석을 사용했다.

해결 방법

/* eslint-disable */
import { setupWorker } from 'msw/browser';

핸들러

  • 핸들러 모습
const handler = () => {
  http.get('가로챌 api 요청 주소', (req, res, ctx) => {
    return HttpResponse.json{ 요청에 대한 response }, code);
  });
};
  • 핸들러 예시
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw'
 
export const handlers = [
  // Intercept "GET https://example.com/user" requests...
  http.get('https://example.com/user', () => {
    // ...and respond to them using this JSON response.
    return HttpResponse.json({
      id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
      firstName: 'John',
      lastName: 'Maverick',
    })
  }),
]

핸들러는 API 요청을 가로채는 것으로, 가로챌 API 요청 method,주소를 가지로 해당 API 요청을 가로채서 보낼 reponse를 넣어주면 된다.

만약 API 요청 주소에 쿼리 파라미터가 들어간다면?

Please match against a path instead and access query parameters using "new URL(request.url).searchParams" instead. Learn more: https://mswjs.io/docs/recipes/query-parameters

쿼리 파라미터가 들어간 API 요청 주소를 사용한다면, 위와 같은 문구가 뜬다.

문구가 뜨지 않게 하려면 어떻게야할까?
MSW는 http에 설정한 주소가 들어간 요청을 가로챈다는 것을 이용하면 된다. 이후에 파라미터 쿼리 값을 추출해, 해당 값에 따라 다른 응답을 내보낼 수 있다.

export const handlers = [
  //1. 파라미터 쿼리 이전의 주소를 이용해 API 요청을 가로챈다.
  http.get('/product', ({ request }) => {
    // 2. request를 사용해 가로챈 API 요청 주소를 확인한다. 
    const url = new URL(request.url)
 // 3. 쿼리 파라미터를 추출한다.
    const productId = url.searchParams.get('id')
	//4. 쿼리 파라미터의 값에 따라 다른 response를 내보낸다.
    if (!productId) {
      return new HttpResponse(null, { status: 404 })
    }
 
    return HttpResponse.json({ productId })
  }),
]

Webpakc mode에 따른 MSW 적용

프로덕션 코드인 React에서 목서버가 필요한 것은 development이다. 배포되는 productio 모드에서는 서버에 API 요청을 해야하기 때문에 MSW가 적용되면 안된다.

Webpack의 mode에 따라 MSW를 다르게 적용하기 위해 사용한 방법은 NODE_ENV환경 변수이다.

  • package.json
"dev": "webpack serve --mode=development --open --hot --progress",
"build": "webpack --mode=production --node-env=production",

dev에서는 NODE_ENV 환경 변수를 설정하지 않아서 그 값이 없지만 production에서는 환경 변수 값이 있다.
이를 이용해, NODE_ENV의 값이 production이 아닐 때 (=dev 일 때) mock 서버를 실행하게 했다.

  • /src/index.tsx
async function enableMocking() {
  if (process.env.NODE_ENV !=='production') {
    const { worker } = await import('./mocks/browser');
    return worker.start();
  }
}
  • 브라우저에서 mock 서버 돌아갈 때 콘솔 창

jest에서 MSW 오류 해결

vitest에서 MSW를 사용했을 때는 오류가 없었는데, Webpack + jest 환경에서 MSW 사용 시 오류가 났었다.

🤯jsdom 사용 오류

해결 방법: jest-fixed-jsdom 설치

MSW는 서비스 워커를 사용해서 네트워크 요청을 가로채는데, jest환경에서 jsdom을 사용할때 서비스워커가 예상대로 작동하지 않을 수 있다. 이런 문제를 해결하기 위해서 jest-fixed-jsdom이 필요하다.

🤯 msw/node를 읽지 못함

해결 방법: jest.config.js의 testEnvironment 빈문자열 추가

// jest.config.js
module.exports = {
  testEnvironmentOptions: {
    customExportConditions: [''],
  },
}

🤯 ReferentError: TextEnCoder is not defined

해결 방법: jest.polyfills.js 추가 및 undici 설치

MSW 공식 문서에 따르면 해당 오류는 Node.js 전역 변수가 없기 때문에 발생한다. 이에 대해 MSW는 jest.polyfills.js 파일을 생성해 적용하고 undici을 설치하라고 설명하고 있다.

🤯 ReferenceError: ReadableStream is not defined

해결 방법: undici 다운 그레이드

TextEnCoder 오류를 해결 하기 위해 공식 문서대로 했는데도 또 다른 오류가 발생했다.
이를 해결하기 위해서 검색으로 찾은 undici 5.0.0 버전 다운 그레이드 방법을 적용해 해당 오류를 잡았다.

🤯 jest의 import 오류

오류 원인

jest는 기본적으로 CommonJS 환경에서 동작하지만, js는 ES 모듈이기 때문에 jest에서 import를 읽어올 때 오류가 난다. 즉, 오류를 해결하기 위해서 ES module을 CommonJS인 jest가 읽을 수 있도록 해줘야한다.

해결 방법 :babel-jest

이를 해결하기 위해서는 package.json에서 "type": "module"을 설정해주면 되지만 이 방법은 .mjs확장자, 동기 import사용 불가등 여러 문제를 일으킬 수 있다.

그래서 선택한 해결책은 babel-jest이다. babel-jest는 ES Modules 및 최신 JavaScript 문법을 포함한 JavaScript 파일을 트랜스파일하는 데 적합하다.

  • jest.config.js
 transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
    '^.+\\.js$': 'babel-jest',
  },
  transformIgnorePatterns: ['<rootDir>/node_modules/'],
  • ts-jest와의 차이?
    ts-jest는 TypeScript 파일을 다루는 데 적합하다. ts-jest만으로는 import와 export가 포함된 JavaScript 파일을 처리하는 데는 부족할 수 있다.

  • jest.polyfills.js에서 import를 required로 수정
import 'dotenv/config';

jest.polyfilles.js에서 'import'를 사용하고 있었다.
해당 import를 읽어올 때 오류가 생겨서 이 부분을 require로 변경했다. 확실한 원인은 모르겠지만 node modules에서 패키지를 사용하는 부분은 require로 설정해야하지 않나하고 추측한다. (패키지가 아닌, src에서 import하는 부분은 문제가 없었다.)

🤯 jest에서 SVG 읽지 못하는 오류

오류 원인

Webpack의 경우 SVG를 loader로 처리 할 수 있다. 그렇지만 jest는 js코드만을 실행하고 처리하도록 설계되어 있어, SVG를 텍스트 파일이 아닌 미디어 파일로 인식한다.

해결 방법: SVG 파일을 Mock으로 처리

모킹할 컴포넌트에서 SVG 파일을 사용할 뿐, jest에서 SVG 파일의 역할은 없다. 그래서 SVG 파일을 대체할 Mock 객체를 만드는 방법으로 오류를 해결했다. jest에서는 모든 SVG 파일들이 Mock 객체로 대체되어서 실행한다.

//svgTransform.js
module.exports = {
  process() {
    return { code: 'module.exports = {};' };
  },
};
//jest.config.js
 transform: {
   // 기존 설정들
    '^.+\\.svg$': '<rootDir>/svgTransform.js',
  },
  transformIgnorePatterns: ['<rootDir>/node_modules/'],

마무리

Webpack환경에서 MSW를 적용하는 것이 쉽지는 않았다.
MSW 버전 업데이트에 따라 Webpack과 호환되지 않는 부분도 있고, eslint와 jest에서도 문제가 있었다. 주말에 이 문제를 붙잡고 풀어야했고, 컴포넌트 테스트 코드를 짤 때에 MSW 관련한 새로운 오류가 발생했다.

MSW 적용 오류를 잡는 것은 쉽지 않았지만, 쉽지 않은 만큼 해결했을때 성취감이 컸다. 환경 셋팅은 막막하게 느껴졌었다. 그러나 막막해도 일단 시도하고 오류도 해결하는 경험들이 쌓이다보니,'일단 해보자!'라는 도전 의식이 생겼다. MSW를 적용하고 나서 느낀 성취감이 팀에서 AWS로 CD를 구축하는 데 지원하는 용기로 이어졌다.

팀에서 MSW 적용을 담당해서 오류를 해결한 지금을 mock서버에 대해서 처음 알게 된 작년과 비교해 보면 이것 또한 하나의 성장이라서 뿌듯하다. 🥰


참고 자료

profile
세상과 사람을 잇는 개발을 꿈꾸는 프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2024년 10월 13일

오늘의 헤르츠는 뭔가요?

1개의 답글