MSW 사용해보기 -1 (+ ApolloClient)

WooJu·2024년 8월 13일

개발

목록 보기
7/12

mock-apollo-client 사용하기

지난 시간에 이어서 이번엔 msw 라이브러리를 사용해서 Mocking을 해보려 한다.

배경


mock-apollo-client의 기술검증을 해보고 있던 중 모킹할 쿼리문에 일일이 mockclient를 생성해서 넣어줘야 하는 부분이 너무 귀찮고 불편해서 더 나은 방법을 찾다가 msw를 알게 되었다.

개발환경
next v14 (page 라우팅)
apolloClient v3.6
msw v2

MSW


Mock Service Worker의 약자
service worker는 별도로 하는 역할이 있는데 이에 대해서는 별도로 정리해볼 예정이다 (처음 본 개념이여서 신기했다)

특징


브라우저가 네트워크를 통해 서버로 보내는 요청을 Service Worker가 중간에 가로채서 미리 생성해둔 응답값을 다시 클라이언트로 보내준다.


구조


...
pages
 ㄴmocks
 	ㄴ handlers.ts
    ㄴ MSWComponent.tsx
public
 ㄴ mockServiceWorker.js (init시 자동생성)

설치


먼저 msw설치

yarn add -D msw

next는 정적파일디렉토리가 /public임으로 worker스크립트에게 별도로 디렉토리를 지정해줘야 한다

npx msw init <public_dir> --save

—save 플래그는?
package.json 파일에 주어진 PUBLIC_DIR을 저장하여 워커 스크립트의 자동 업데이트를 위해 다음과 같이 설정할 수 있습니다.

msw를 실행시키면 ./public 디렉토리를 저장할것이다

package.json 파일에 msw.workerDirectory 속성을 추가하여 msw 패키지를 설치할 때마다 워커 스크립트를 자동으로 지정된 디렉토리로 복사하도록 설정할 수 있습니다. 이렇게 하면 현재 설치된 라이브러리 버전과 워커 스크립트가 동기화됩니다.

// package.json
{
  "name": "my-app",
  "msw": {
    "workerDirectory": "./public"
  }
}

🦺 service worker 초기세팅


MSWComponent를 만들어서 브라우저일때 serviceWorker를 실행시키고 서버일때는 serviceWorker가 실행되지 못하기에 핸들러와의 bridge역할을 해서 모의응답값을 보내주도록 한다.

//pages/mocks/MSWComponent.tsx
import {setupServer} from "msw/node"
import {setupWorker} from "msw/browser"

const MSWComponent=()=>{
  const setupHandler = () => {
    if (typeof window === "undefined") {
      const server = setupServer(...handlers);
      server.listen();
    } else {
      const worker = setupWorker(...handlers);
      worker.start();
    }
  setupHandler()
  return null
}

export default MSWComponent

가장 최상단 파일에서 호출해주면 된다.

//pages/_app.tsx

const App=()=>{
return (
	<div>
		<MSWComponent/>
		{children}
	</div>
}

❌ 위처럼 해줬더니 에러가 발생한다 ❌


Module not found: Package path ./node is not exported from 
package /.../node_modules/msw 
(see exports field in /.../node_modules/msw/package.json)
...
  14 | import { setupWorker } from "msw/browser";
> 15 | import { setupServer } from "msw/node";
     | ^

설명을 읽어보면 클라이언트에서 setupServer가 실행되고
서버에서 setupWorker가 실행된다는 의미이다
typeof window로 분기를 쳐줬는데도 저 에러가 뜨는건 이해가 안된다

✅ 해결방법


❌ 방법1) 웹팩에서 서버일때는 msw/browser 모듈을 읽지 않고 클라이언트일때는 msw/node 모듈을 읽지 않도록 강제한다.

  • serviceWorker작업이 동기적으로 발생됨
  • 환경에 따라 모듈을 억제 (브라우저일때는 서버모듈 importX,서버일때는 브라우저 모듈 importX)
  //next.config.js
  webpack: (config, { isServer }) => {
    if (isServer) {
      // next server build => ignore msw/browser
      config.resolve.alias["msw/browser"] = false;
    } else {
      // browser => ignore msw/node
      config.resolve.alias["msw/node"] = false;
    }
		...
    return config;
  },
 
};

위 방법으로 진행해서 에러는 사라졌지만 Mocking연결이 안됬다

⭕️ 방법2) 최상단이 아닌 실행시점에 모듈을 import한다 (+ 즉시실행)

//_app.tsx
  if (typeof window === "undefined") {
    (async () => {
      const { setupServer } = await import("msw/node");
      const server = setupServer(...handlers);
      server.listen();
    })();
  } else {
    (async () => {
      const isServiceWorker = await navigator.serviceWorker.getRegistrations();
      if (!isServiceWorker.length) {
        const { setupWorker } = await import("msw/browser");
        const worker = setupWorker(...handlers);
        worker.start();
      }
    })();
  }

⭕️ 방법3) 브라우저모듈은 (msw/browser)는 useEffect내부에서 import,
서버모듈은 (msw/node) instrumentation.ts파일에서 import

  • next.config.js에서 강제로 import를 막는것이 아닌 브라우저모듈은 브라우저에서만 실행되고 서버모듈은 서버에서만 실행이 되도록 한 방법이다

✌️브라우저

// mocks/MSWComponent.tsx

import { initMsw } from "mocks";
import { useEffect } from "react";
import { handlers } from "./handlers";

const MSWComponent = () => {
  useEffect(() => {
    const msw = async () => {
      if (
        typeof window !== "undefined" &&
        process.env.NEXT_PUBLIC_REGION === "develop"
      ) {
    		navigator.serviceWorker.getRegistrations().then(async res => {
      			if (!!res.length) {
        			return;
      			} else {
        			const { setupWorker } = await import("msw/browser");
        			const worker = setupWorker(...handlers);
        			worker.start();
      			}
    		});
      	}
    };
    msw();
  }, []);

  return null;
};

export default MSWComponent;

navigator.serviceWorker.getRegistrations() 는 현재 등록된 serviceworker리스트를 보여주는 함수로 현재 등록된 serviceWorker가 없을때만 등록되도록 함

//pages/_app.tsx

const App=()=>{
return (
	<div>
		<MSWComponent/>
		{children}
	</div>
}

✌️서버

instrumentation은 next14기준 실험용기능이기 때문에 next-config.js에서 별도로 설정을 추가해줘야 한다.

//next-config.js

const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
  webpack(config) {
    return config;
  },
};

instrumentation은 서버 인스턴스가 실행될때 단 한번만 실행되는 곳으로 주로 로깅,모니터링관련 기능을 작업과정이 담긴다.
(단 한번만 실행됨으로 hot-reload시에는 실행되지 않음)

//instrumentation.ts 

import { handlers } from "./mocks/handlers";

export async function register() {
  if (
    process.env.NEXT_RUNTIME === "nodejs" &&
    process.env.NEXT_PUBLIC_REGION === "develop"
  ) {
    const { setupServer } = await import("msw/node");
    const server = setupServer(...handlers);
    server.listen();
  }
}

결론

위 두 해결방안으로 하면 해당에러는 더이상 나타나지 않는다.
아래와 같은 화면을 콘솔창에서 확인한다면 Mocking할 준비가 되었다

다음 포스터에는 실제로 네트워크요청을 가로채서 미리 만들어둔 응답값을 리턴해보겠다.

MSW 사용해보기 -2 (+ ApolloClient)

profile
모르는게 너무 많아

0개의 댓글