React Native에서 MSW 사용하기

GwangSoo·2025년 2월 23일
3

개인공부

목록 보기
20/34
post-thumbnail

프로젝트를 진행하다 보면 API가 나오기 전에 가짜 데이터를 통해 데이터를 보여주고 싶은 상황이 있을 것이다.

이번 글에서는 MSW를 이용하여 API를 mocking하는 과정에 대해 이야기해 보겠다.

🤔 mocking이란?
진짜 서버나 API 없이도, 마치 있는 것처럼 동작하도록 가짜 데이터를 만들어 테스트하거나 개발하는 것

MSW란?

MSW(Mock Service Worker)API를 모킹(mocking)할 수 있는 라이브러리로, 특정 클라이언트에 종속되지 않는 모킹을 작성하여 다양한 프레임워크, 도구, 환경에서 재사용할 수 있도록 해준다.

여기서 특정 클라이언트란 아래의 것들을 의미한다.

  • 웹 브라우저
  • Node.js 환경
  • React Native 앱
  • 테스트 환경 (Jest, Cypress, Playwright 등)

이제 MSW가 무엇인지 알아보았으니 설정을 진행해 보겠다.

설정

설정은 MSW의 React Native Integration을 참고하여 진행했다.

아래의 패키지를 설치하고, 루트에 코드를 추가해 준다.

npm install react-native-url-polyfill fast-text-encoding
// msw.polyfills.js
import 'fast-text-encoding'
import 'react-native-url-polyfill/auto'

위에서 설치한 라이브러리들은 app.config.ts(app.json)에 추가로 설정할 필요가 없다.

mocking을 위한 환경을 세팅해 줘야 하기 때문에 아래 코드를 추가로 생성해 준다.

// mocks/server.ts
import { setupServer } from 'msw/native'
import { handlers } from './handlers'
 
export const server = setupServer(...handlers)

마지막으로 프로젝트를 감싸는 레이아웃 파일에 아래 코드를 추가해 준다. 나의 경우 최상단에 있는 _layout.tsx을 감싸고 있는 root-provider.tsx라는 파일 안에 아래 코드를 추가해 주었다.

async function enableMocking() {
  if (!__DEV__) {
    return
  }
 
  await import('msw.polyfills 경로') // ex) ../../../msw.polyfills
  const { server } = await import('mocks/server.ts 경로') // ex) ./src/mocks/server
  server.listen()
}
 
enableMocking().then(() => {
  AppRegistry.registerComponent(appName, () => App)
})

이렇게 하면 세팅은 끝이다.

🚨 트러블 슈팅

ReferenceError: Property 'MessageEvent' doesn't exist

위처럼 세팅을 하고 npx expo run:ios를 하게 되면 아래처럼 에러가 발생할 것이다.

mock-error

위 에러는 해당 속성이 React Native 환경에 없다는 것을 의미한다. MessageEvent 이외에도 없는 속성들이 있었기 때문에 해당 속성들을 추가해 주는 코드를 msw.polyfills.js에 작성했다.

import "fast-text-encoding";
import "react-native-url-polyfill/auto";

function defineMockGlobal(name) {
  if (typeof global[name] === "undefined") {
    global[name] = class {
      constructor(type, eventInitDict) {
        this.type = type;
        Object.assign(this, eventInitDict);
      }
    };
  }
}

["MessageEvent", "Event", "EventTarget", "BroadcastChannel"].forEach(defineMockGlobal);

ReferenceError: Property 'Document' doesn't exist

import axios from "axios";

useEffect(() => {
  const fetchItem = async () => {
    try {
      const response = await axios.get("mock api url");
      console.log("mock response: ", response.data);
    } catch (err) {
      console.error(err);
    }
  };
  fetchItem();
}, []);

mock-error

axios를 이용해서 mock api에 요청을 하면 위와 같은 에러가 나온다. 이는 React Native 환경에서 axios의 동작과 msw/native의 interceptor의 동작 방식에 대해 찾아보니 이해가 되었다.

우선 React Native 환경에서 axios의 기본 adapter는 XHR HttpRequest이다. 아래는 msw/native의 setupServer 코드 일부분이다.

export function setupServer(
  ...handlers: Array<RequestHandler>
): SetupServerCommonApi {
  // Provision request interception via patching the `XMLHttpRequest` class only
  // in React Native. There is no `http`/`https` modules in that environment.
  return new SetupServerCommonApi(
    [FetchInterceptor, XMLHttpRequestInterceptor],
    handlers,
  )
}

mswjs/msw - msw/src/native/index.ts

msw/native에서는 fetch나 xhr interceptor를 이용해서 클라이언트의 요청을 인터셉트한다. XMLHttpRequestInterceptor에 대해 조금 더 알아보니 아래와 같은 코드가 있었다.

get responseXML(): Document | null {
  invariant(
    this.request.responseType === '' ||
      this.request.responseType === 'document',
    'InvalidStateError: The object is in invalid state.'
  )

  if (this.request.readyState !== this.request.DONE) {
    return null
  }

  const contentType = this.request.getResponseHeader('Content-Type') || ''

  if (typeof DOMParser === 'undefined') {
    console.warn(
      'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'
    )
    return null
  }

  if (isDomParserSupportedType(contentType)) {
    return new DOMParser().parseFromString(
      this.responseBufferToText(),
      contentType
    )
  }

  return null
}

mswjs/interceptors - src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts

responseXML반환값이 Document | null로 되어있는 것이었다. 이 부분 때문에 현재와 같은 에러가 발생했다고 생각했다.

그래서 axios의 adapter를 fetch로 변경하였더니 정상적으로 데이터를 가져오는 것을 확인했다.

import axios from "axios";

const instance = axios.create({
  adapter: "fetch",
});

useEffect(() => {
  const fetchItem = async () => {
    try {
      const response = await instance.get("mock api url");
      console.log("mock response: ", response.data);
    } catch (err) {
      console.error(err);
    }
  };
  fetchItem();
}, []);

mock-success

마무리하며

웹 환경에서 msw를 사용할 때에는 설정이 번거롭지 않았는데, React Native 환경에서 설정하다 보니 마주한 에러들이 생각보다 번거로웠다. 하지만 덕분에 axios와 msw의 동작 과정에 대해 조금 더 알아볼 수 있는 기회가 되어 좋았다.

5개의 댓글

comment-user-thumbnail
2025년 5월 16일

안녕하세요 생명의 은인이십니다!!
혹시 없는 property를 추가해주는 코드는 어떤 방식으로 접근하셨을까요?
참고하신 리소스가 있을까요?
감사합니다.

1개의 답글
comment-user-thumbnail
2025년 5월 17일

동일한 문제에 대한 issue가 opened 되어 있어서, 본 블로그의 코드를 첨부하였습니다.
https://github.com/mswjs/mswjs.io/issues/453

답글 달기
comment-user-thumbnail
2025년 8월 19일

감사합니다. 큰 도움 되었습니다!

1개의 답글