올리브영 테크블로그 포스팅 Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기
Frontend

Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기

네트워크 Mocking을 위해 고민했던 삽질기

2024.01.23

안녕하세요~ 올리브영 커머스 서비스 개발팀에서 프론트엔드개발을 하고 있는 개발새발자 입니다~ 만반잘부 👋🏼👋🏼👋🏼

이번 포스팅에서는 Next.JS에 MSW를 도입하면서 고민했던 내용들과 삽질했던 내용들을 여러분에게 소개하겠습니다!!

그럼 긴 말은 필요 없습니다

let's go ~!


출처 : https://media.giphy.com/media/cdNSp4L5vCU7aQrYnV/giphy.gif

개요

현재 올리브영 프론트엔드파트는 클래식 아키텍처인 Spring MVC에서 벗어나 모던 아키텍처인 Next.js 기반으로 전환하는 대규모 마이그레이션 작업을 진행 중에 있습니다.

이러한 변화는 단순히 기술 스택의 전환을 떠나, 프론트엔드 개발자로서 성장할 수 있는 여러 과제를 제공하는데요.

특히 클래식 아키텍처에서는 미처 다루지 못했던 단위 테스트 작업 또한 프론트엔드파트에서 열심히 진행하고 있는 주요 과제 중 하나입니다.

하지만, 이 과정에서 예상치 못한 문제가 발생했습니다.

단위 테스트를 작성하는 과정에서 만난 첫 번째 고민이 바로 MSW(Mock Service Worker)의 도입을 고려하게 만든 계기가 되었습니다.

고민 1 - 불필요한 코드 및 중복 코드 증가

첫 번째 고민은 바로 불필요한 코드 및 중복 코드의 증가입니다.

특히 테스트 코드를 작성할 때 고민이 깊어졌는데요 😭

단위 테스트를 작성 하다보면, API 서버 통신과 의존된 기능들을 만날 수 있을겁니다.

올리브영에서는 axios-mock-adapternock 같은 API 통신을 모의하는 라이브러리를 사용하지 않고, 자체적으로 API를 모의하여 테스트를 진행했었는데요

보통 아래 코드와 같은 방식으로 작성했습니다.


import * as axios from "axios";

// axios 모듈을 Mocking합니다.
jest.mock("axios");


test("Axios 테스트 1", () => {
    // API를 Mocking합니다.
    axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
    ...
});

test("Axios 테스트 2", () => {
    // API를 Mocking합니다.
    axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
    ...
});

위 코드의 문제점이 보이시나요?

아마 지금은 보이지 않으실 겁니다.

하지만, 이러한 코드들을 계속해서 작성하다 보면, 비슷한 데이터를 반환하는 API 모의 코드들이 다양한 파일들에 산재되고 있다는 것을 느낄수 있고, 유지관리 또한 잘 안되는 것을 느끼실 수 있을 겁니다.

물론 최대한 공통화 및 모듈화를 통해서 중복된 부분을 어느 정도 해소할 수 있겠지만, API를 Mocking하기 위한 불필요한 작업들이 존재하게 됩니다.

이러한 작업들이 지속될수록 단위 테스트를 작성하는 게 피로해지고 King(👑) 받게 됩니다.

그럼 다음 고민으로 넘어가 볼까요?

고민 2 - 개발 일정이 빠듯해요!!


모든 일은 항상 예상했던 것보다 오래 걸린다. 심지어 호프스태터의 법칙을 고려해서 계획을 세웠다 해도 말이다

- 호프스태터의 법칙(Hofstadter's Law)


개발자로 서비스 개발을 하다 보면 한 번쯤은 개발 일정이 빠듯했던 경험이 있지 않나요?

아마 대부분의 개발자는 이러한 경험이 있을 것이며, 개발 일정이 빠듯할 경우 개발 일정을 미루거나 크런치 모드를 해 겨우 일정을 맞췄던 경험이 있을 거라 생각합니다.

그럼 왜 이렇게 개발 일정이 빠듯할까요?

보통 프로젝트의 착수하면 아래와 같은 플로우로 진행하게 됩니다.



프로젝트 개발 플로우 차트
프로젝트 개발 플로우 차트

1. PO가 프로젝트의 요구사항을 분석한 후 내용을 정리하여 디자이너, 백엔드 개발자, 프론트엔드 개발자에게 전달합니다
2. 디자이너는 기획자가 정리한 요구사항을 가지고 디자인을 한 다음 프론트엔드 개발자, 백엔드 개발자에게 전달합니다
3. 백엔드 개발자는 요구사항 및 디자인을 기반으로 API를 개발합니다
4. 프론트엔드 개발자는 요구사항 및 디자인을 기반으로 화면을 구성합니다
5. 프론트엔드 개발자가 화면 구성을 완료하게 되면 백엔드와 API를 연동해야 하지만, 백엔드의 개발이 길어지면 API가 개발되어 질 떄 까지 대기를 합니다
6. 백엔드 개발자가 API를 개발을 일정 부분 완료하면 프론트엔드는 API를 연동합니다
7. 그 사이 개발 마감 기한을 지나치게 됩니다
8. QA도 덩달아 마감 기한에 시달리게 됩니다
9. 💣 Bomb!!!


제가 스토리텔링을 매우 극단적으로 작성하긴 했지만, 종종 발생하는 상황이기도 하며, 저도 종종 겪었던 상황이라 이렇게 작성해 보았습니다.

그럼, 여기서 문제는 무엇일까요?? 🤔

바로 프론트엔드가 화면을 구성한 후 백엔드를 개발이 완료될 때 까지 대기시간이 존재한다는 것입니다.

그럼 아래 사진과 같은 상황이 발생하겠죠??🐧



API 언제 나와요?

물론! 저 잉여 시간이 꼭 프로젝트의 마감기한을 넘기는 문제는 아닐 수 있지만, 개선할 점이 존재하는 건 맞습니다.

그러면, 몇몇 분들은 Mock 데이터나 Mock API를 만들어서 사용하면 되는 거 아니냐! 하실 수 있습니다.

맞습니다! 하지만, 말씀하신 내용이 MSW를 도입하게 된 이유입니다.

고민 3 - Mock 데이터 연동이 귀찮고 문제가 많아요!

올리브영은 Next.js 프레임워크를 사용해서 프론트엔드를 개발하고 있습니다.

많은 프론트엔드 개발자들은 아시겠지만, Next.js는 프론트엔드 노드 서버를 제공하기에 보통 Mock Data가 필요할 경우 Next.js의 노드 서버를 이용해 Mock API를 만들어서 사용합니다.

저희 또한 그랬고요..

뭐든지 그렇듯 처음에는 잘 사용했지만, 시간이 지나면서, 다양한 문제와 피로감을 느끼기 시작했습니다.





그럼 그 문제와 피로감은 무엇이었을까요?

풀어서 설명하면 한도 끝도 없기에 간략하게 설명하자면 아래와 같습니다.


- 프로젝트에 착수할 때마다 매번 동일한 Mock API를 생성해 주고 사용 후 꼭 삭제해야 하는 피곤함
- Mock API를 적용할 때는 Mock API 주소를 적용해야 하고 백엔드 API 주소로 변경해야 하는 귀찮음
- 휴먼에러로 Mock API 주소를 바꾸지 않고 배포해 서비스가 Mock API 데이터로 보이는 황당함
- Next.js 빌드 시 Mock API 내용도 포함되어 사이즈가 커지는 당혹감

특히 Mock API주소로 배포를 하는 휴먼 에러와 Next.js 빌드 시 Mock API 내용도 포함되어 빌드 사이즈가 커지는 이슈는 해결이 필요해 보였습니다.



webpack-analyzer 결과
빌드 시 `webpack-analyzer`를 통해 나온 테스트 Mock api

그럼 올리브영은 위 이슈들을 어떻게 해결했을까요??

고민 그리고 리서칭

처음에는 직접 개발해 위 고민들을 해결해 볼까 라는 생각을 하였지만, 한정된 리소스 내에서 직접 개발하기엔 너무 리소스가 부족하고 힘들 것 같았습니다.

따라서 저의 리소스를 투입해서 해결하는 방법보단, 외부 오픈소스 라이브러리를 활용하여 이슈를 해결하기로 마음먹었습니다.

그 중 리서칭한 몇 가지의 패키지(혹은 툴)들을 소개해 드리겠습니다.

Postman Mock Server

포스트맨(Postman) 이란, API를 개발하면 한번쯤 사용하게되는 API 플랫폼으로써 API 요청 테스팅을 비롯해서 디버그, 문서화 등 다양한 기능들을 제공하는 플랫폼입니다.

저는 다양한 기능들 중 포스트맨 목 서버(Postman Mock Server)에 주목을 했는데요, 이 기능은 말 그대로 포스트맨을 이용해서 Mock API Server를 생성하여 실제 API서버처럼 클라이언트의 요청을 받은 후 Mock Data를 반환하는 기능입니다.

이 기능을 이용하면 충분히 프론트엔드 개발의 유휴기간을 해소할 수 있고, Next.js 빌드 파일 사이즈 이슈를 해결할 수 있을것 같았지만, 각 개발자가 모의 서버를 만들어야 하며, API를 Mcoking하여도 다른 개발자들과의 공유가 불편하다는 단점과 API주소를 특정해야해서 휴먼에러가 발생할 수 있다는 단점이 있습니다.

그리고, Postman Mock Server를 기업에서 사용하기 위해선 플랜에 가입해야한다는 부분에서 제외하기로 결정했습니다.

Nock

Nock 이란 HTTP의 요청을 가로채고 모방하는 패키지로서, 노드 환경에서 동작하며 주로 HTTP 요청에 따라 처리하는 로직이 달라지는 모듈을 테스트하는 데 사용하는 패키지입니다.

하지만, 노드에서만 돌아간다는 부분과, 단위 테스트 작성 시 충분히 도움이 되지만, 그 외에는 도움이 되질 않아 도입은 보류하기로 결정하였습니다



-

사실 위 두 가지 말고도 axios-mock-adapterPrism 같은 여러 패키지들도 많이 존재하지만 Postman Mock Server나 Nock에서 설명했던 내용과 비슷한 이유로 후보에서 제외했습니다.

계속 리서칭하며 인터넷 세계를 헤매고 지쳐가던 중, MSW(라고 하고 그저 빛이라고 읽는다)✨라는 오픈소스 라이브러리를 만났습니다.

MSW 넌 뭐냐?(올리브영에서 MSW를 선택한 이유)


출처 : https://youtu.be/HcQCqboatZk?si=WQvy1tdK_8OeZ4LS

먼저 MSW를 간략하게 설명드리자면, MSW(Mock Service Worker)란, 클라이언트가 HTTP 요청을 전송하면 Service Worker가 요청을 가로챈(intercept) 후 Mocking된 응답 값을 반환함으로써 서버와의 통신을 모방하는 오픈소스 라이브러리입니다.

전체적인 모습은 위에서 설명한 Nock이랑 비슷하지만, 노드에서 동작하는 Nock 과는 달리 노드뿐만 아니라 브라우저에서 실제 HTTP 요청을 가로챌 수 있는 부분이 다른데요

실제 HTTP 요청을 가로챌 수 있는 이유는 MSW의 주요 특징인 서비스 워커를 사용하기 때문입니다.


서비스 워커(Service Worker)

웹 페이지와 별도로 브라우저가 백그라운드에서 실행되는 스크립트로 응용 프로그램, 브라우저, 그리고 네트워크 사이의 프록시 서버 역할을 합니다


저는 처음 MSW를 발견했을 때, MSW에 대해서 Nock과 비슷한 라이브러리라 생각해 크게 관심을 가지지 않았습니다.

하지만, 파일럿 테스트를 하면서 MSW는 점점 저를 매료시켰고 파일럿 테스트를 끝냈을 때 들었던 생각은 이 패키지를 사용하면 위에서 고민했던 내용들을 모두 해결할 수 있겠다! 였습니다.

특히 별생각 없던 기능인 브라우저에서 실제 네트워크를 모방할 수 있다는 점이 저에게 가장 큰 매력적인 기능이었고 MSW를 본격적으로 도입하기로 마음먹었습니다.

그럼 올리브영 프론트엔드 프로젝트에서 MSW를 도입하며 겪었던 내용에 대해 알아봅시다.

MSW 도입 그리고 삽질기, 사소한 Tip!

지금까지, 제가 했던 고민들, 그리고 리서칭한 내용들을 설명드렸습니다.

이제 본격적으로 제가 MSW를 도입하면서 삽질했던 내용과 사소한 Tip들을 공유해 보겠습니다.

도입 - MSW 프로젝트에 적용하기

사실, MSW를 프로젝트에 적용하는 건 MSW 공식 문서에 가장 정리가 잘 되어 있기에 간단하게 설명드리겠습니다.

먼저 프로젝트에서 사용하는 패키지 매니저를 통해 MSW를 설치합니다.

(올리브영 온라인몰의 패키지 매니저는 yarn이므로 yarn을 기준으로 설명드리겠습니다)


$ yarn add -D msw

그러면 MSW가 설치 되는 것을 확인할 수 있습니다.

그 후, MSW에서 사용할 서비스 워커 파일인 mockserviceworker.js를 생성하기 위해 사진의 표를 참고하여, 터미널의 커맨드를 입력해줍니다.

(Next.js는 public 폴더가 정적 파일 디렉토리이므로 <PUBLIC_DIR>public으로 변경합니다)



public 디렉토리 표
출처 : https://v1.mswjs.io/docs/getting-started/integrate/browser#setup

$ npx msw init <PUBLIC_DIR> --save

# next.js일 경우
$ npx msw init public/ --save

그러면 아래 사진과 같이 mockserviceworker.js가 생성된 것을 확인할 수 있습니다.



mock service worker 생성 시
mockserviceworker 생성한 모습


이제 마지막으로 _app.tsx에 아래와 같이 추가해 주세요


// src/pages/_app.tsx
...
if (typeof window === 'undefined') {
    const server = setupServer([...핸들러작성...]);
    server.listen()
} else {
    const worker = setupWorker([...핸들러작성...]);
    worker.start()
}
...
function App(...) {};

그러면 아래 사진과 같이 [MSW] Mocking enabled.라는 문구를 콘솔에서 확인할 수 있습니다.



mocking enabled

이제 Mocking하고 싶은 API 핸들러를 setupServer혹은 setupWorker의 작성하기만 하면!!!


축하합니다! MSW를 성공적으로 도입하셨습니다!!🎉

만약, 이렇게 간단한 내용이었다면 이 글을 작성하지 않았겠죠??....😢



nolife

사실, 별다른 설정이 없는 프로젝트에서는 이렇게 간단하게 적용할 수 있지만,

실제 운영 중인 프로젝트에서는 여러 이해관계와 유관부서들이 엮여 여러 가지 설정들이 적용되어 있기에 위와 같이 적용하면 정상적으로 동작을 안 할 가능성이 상당히 높습니다.

이번에는 올리브영 프로젝트에 MSW를 도입하면서 삽질했던 내용과 사소한 TIP들을 알려드리겠습니다.

삽질 1 - 프록시로 인한 API 주소 컨트롤 불가 이슈

저는 처음 MSW를 도입하기로 결심했을 때, 공식 문서에 내용이 많지 않아서 큰 리소스를 투입하지 않을 거라 생각했습니다.

하지만....

짜잔! 브라우저에서 실행하니 아래와 같은 에러가 발생하네요... 🫠



msw error
공식문서 따라서 도입 후 발생한 에러


에러 내용은 아래와 같습니다.


If you still wish to intercept this unhandled request, please create a request handler for it.

작성되지 않은 리퀘스트 핸들러를 가로채고 싶다면, 요청 처리기를 만드세요


그런데 이상하네요? 🤔

제가 이 글에서 핸들러 코드를 작성하지 않았지만 실제 코드에선, setupWorker(...)에 핸들러를 등록했고 핸들러 코드 로직에서 에러가 터질만한 로직도 없는데 위와같은 에러가 발생하고 있었습니다.

계속, 삽질해 본 결과 올리브영 프로젝트 내에서 프록시를 통해 API를 호출하기 때문에 Mocking한 핸들러 주소가 올바르지 않았던 이슈라는 걸 발견했습니다!

좀 더 자세히 설명해볼까요?

올리브영 프론트엔드는 Next.js에서 제공하는 rewrites(프록시)을 통해 API를 호출하고 있습니다.

따라서, 프론트엔드 서버로 들어오는 API 요청들은 모두 아래와 같은 순서로 동작합니다.



proxy1

1. 브라우저 환경에서 "https://<프론트엔드 서버 주소>/api/v1/test" URI로 API를 요청합니다 
2. "https://<프론트엔드 서버 주소>/api/v1/test"에 해당하는 서버(프론트엔드 서버)로 API를 요청합니다
3. 프론트엔드 서버는 API서버로 프록시 해야하는 주소인지 확인합니다
4. API 서버로 보내야하는 주소이므로, API서버 주소인 "https://<API 서버 주소>/api/v1/test"로 프록시 합니다
5. API 서버는 "https://<API 서버 주소>/api/v1/test" URI에 해당하는 데이터를 프론트엔드 서버에 반환합니다
6. 프론트엔드 서버는 그대로 유저에게 반환합니다

하지만, 노드 환경 즉, SSR시 필요한 데이터를 호출할 때는 아래와 같은 순서로 동작합니다.





1. 노드 환경에서 "https://<API 서버 주소>/api/v1/test" 호출합니다
2. "https://<API 서버 주소>/api/v1/test"에 해당하는 서버(API 서버)로 API를 요청합니다
3. API서버는 "https://<API 서버 주소>/api/v1/test" URI에 해당하는 데이터를 반환합니다

정리하자면 같은 API를 호출하더라도 환경에 따라서 브라우저 환경에서는 https://<프론트엔드 서버 주소>로 노드 환경에서는 https://<API 서버 주소>로 호출하기에 위와같은 에러가 발생한 상황이었습니다.

그렇다면 이걸 어떻게 해결했을까요?

해결 방법에 대해서 리서칭을 해보았지만, 확실한 답이 나오지 않아, 자체적으로 주소를 변경하는 MSW 클라이언트를 만들었습니다.

그 코드는 아래와 같습니다.


import { DefaultBodyType, MockedRequest, PathParams, ResponseResolver, RestContext, RestHandler, RestRequest, rest } from "msw";

// Mocking Rest Handler에 타입
type MockingRestHandler<
    RequestBodyType extends DefaultBodyType = DefaultBodyType,
    Params extends PathParams<keyof Params> = PathParams,
    ResponseBody extends DefaultBodyType = DefaultBodyType
> = (
    path: string,
    resolver: ResponseResolver<RestRequest<RequestBodyType, Params>, RestContext, ResponseBody>
) => RestHandler<MockedRequest<DefaultBodyType>>;

// MockRestClient을 정의하는 인터페이스
interface IMockRestClient {
    get: MockingRestHandler;
    post: MockingRestHandler;
    put: MockingRestHandler;
    delete: MockingRestHandler;
    patch: MockingRestHandler;
    options: MockingRestHandler;
}

// MockRestClient는 RESTful API 작업을 Mocking하기 위한 Client
const MockRestClient = (() => {
    // 환경(서버/클라이언트)에 따라 적절한 URL을 반환하는 함수
    const resolveURL = (url: string): string => {
        if (typeof window === "undefined") {
            // 노드 환경이면 서버 URL을 붙여서 반환한다.
            return `${서버주소}${url}`;
        }
        // 클라이언트 환경이면 그대로 반환한다.
        return url;
    };

    // 주어진 URL이 FULL URL인지 확인하는 함수
    const isExternalUrl = (url: string): boolean => {
        return url.match(/^(https|http)/g) !== null;
    };

    // Mocking API URL을 정재하여 반환하는 함수
    const getMockApiUrl = (url: string): string => {
        // 외부 URL이면 그대로 반환하고 아니면 Mocking API URL로 변환하여 반환한다.
        return isExternalUrl(url) ? url : resolveURL(url);
    };

    // Mocking API를 위한 Client
    const _mockRestClient: IMockRestClient = {
        get: (url, resolver) => rest.get(getMockApiUrl(url), resolver),
        post: (url, resolver) => rest.post(getMockApiUrl(url), resolver),
        put: (url, resolver) => rest.put(getMockApiUrl(url), resolver),
        patch: (url, resolver) => rest.patch(getMockApiUrl(url), resolver),
        delete: (url, resolver) => rest.delete(getMockApiUrl(url), resolver),
        options: (url, resolver) => rest.options(getMockApiUrl(url), resolver),
    };

    return _mockRestClient;
})();

export { MockRestClient };

주석이 있지만, 대략적으로 설명하자면,

핸들러를 등록할 때, 핸들러 등록 환경이 노드환경일 경우 서버주소를 반환하고, 브라우저 환경일 경우 프론트엔드 서버 주소를 반환하도록 처리한 코드입니다.

그리고 아래 코드는 핸들러를 작성할 때의 코드입니다.


...
MockRestClient.get(`/api/v1/test`, (req, res, ctx) => {
    return res(ctx.json(MOCK_RESPONSE));
})
...

이렇게 처리를 하니, 각 환경에 따라 핸들러의 주소가 동적으로 변환되어 등록할 수 있게 되었습니다.

삽질 2 - URL 프리픽스로 인한 mockServiceWorker를 못찾는 이슈

두 번째 삽질은 URL 프리픽스로 인해 발생한 이슈입니다.

보통 서비스 워커의 동작을 정의하기 위해 sw.js라는 파일을 생성합니다.

MSW에서도 서비스 워커를 사용하기에 mockserviceworker.js를 생성하여 서비스 워커의 동작을 정의하는데,

MSW를 실행시키니 mockserviceworker.js 파일을 찾을 수 없다는 에러가 발생했습니다.



mockServiceWorker
개발환경에서 mockServiceWorker를 찾지 못하는 이슈

[MSW] Failed to register a Service Worker for scope ('http://localhost:3000/') with script ('http://localhost:3000/mockServiceWorker.js'): Service Worker script does not exist at the given path.

[MSW] 스크립트('http://localhost:3000/mockServiceWorker.js')를 사용하여 범위('http://localhost:3000/')에 대한 서비스 워커를 등록하지 못했습니다: 지정된 경로에 서비스 워커 스크립트가 없습니다.


처음에는 왜 public 폴더에 있는 mockserviceworker.js를 찾을수 없는지 고민을 했습니다.

하지만, 정답은 언제나 근처에 있는 법, 올리브영에서 사용하는 프리픽스가 문제라는 것을 파악할 수 있었습니다.

올리브영 모바일 웹을 들어가 보신적 있으신가요?

올리브영 모바일 웹 주소는 https://m.oliveyoung.co.kr/m/mtn으로 구성되어져 있습니다.

여기서 중요한 부분은 /m/mtn인데요,

/m/mtn이 URL의 프리픽스 잡혀있어서 MSW가 Next.js public폴더에 접근하기 위해서는 https://m.oliveyoung.co.kr/m/mtn/mockServiceWorker.js로 접근해야만public에 접근할 수 있습니다.

하지만 MSW는 기본적으로 루트 경로(http://<프론트엔드 서버 주소>/)에서 mockserviceworker.js를 찾도록 되어져 있어서 위같은 에러가 발생한 것입니다.

위 문제를 어떻게 해결할까... 한참을 삽질하던중 MSW 레포지토리에서 관련내용을 찾았습니다.

(유레카 😭)



discussion
MSW 레포지토리 Discussion


독자들의 시간은 소중하니, 해당 페이지의 내용을 대략적으로 요약하자면, mockServiceWorker.js의 위치가 변경되면, 네트워크를 가로챌 수 있는 영역(이하 Scope)달라지며, 달라진 Scope이외의 Scope의 요청은 인터셉트할 수 없기에 최대한 루트 경로에 있는것을 추천한다는 내용입니다.

아마 위 내용만 봤을때는 무슨말인지 햇갈리실거라 생각합니다.

(아니면 당신은 천재✨)

다시말해서, mockServiceWorker.js위치가 /에서 /m/mtn으로 변경되면 변경된 위치(/m/mtn)가 아닌 /product/map같은 페이지에서는 해당 서비스 워커를 사용할 수 없다는 이야기입니다.(/m/mtn/product는 접근이 가능👍)

하지만 올리브영에서는 프리픽스인 /m/mtn 페이지가 아닌 다른 페이지에서 MSW의 서비스 워커가 사용되지 않기에 Service Worker를 실행할 때 아래와 같이 작성했습니다.


...
const worker = setupWorker([...핸들러작성...]);
worker.start({
  serviceWorker: {
    // `mockServiceWorker.js`가 생기는 위치
    url: "/m/mtn/mockServiceWorker.js",
    options: {
      // `/m/mtn`이하 페이지로 scope를 제한
      scope: "./",
      ...
    },
  },
})
... 

위와같이 처리를 하니 정상적으로 동작하는 것을 볼 수 있었습니다.



msw 2

-

여기까지 MSW를 적용하면서 겪었던 삽질기를 공유했습니다.

그럼, 이제는 제가 MSW를 사용하면서 공유드릴 사소한 Tip들을 소개시켜드리겠습니다.

(올리브영에서도 아래 설명한 Tip들을 기반으로 구성되어 있어요(소곤소곤))

TIP 1 - 서비스 워커와 노드서버 setup코드는 분리하여 관리합니다

위에서 MSW를 적용할 때 아래와 같은 코드를 작성하시라고 말씀드렸습니다.


// src/pages/_app.tsx
...
if (typeof window === 'undefined') {
    const server = setupServer([...핸들러작성...]);
    server.listen()
} else {
    const worker = setupWorker([...핸들러작성...]);
    worker.start()
}
...
function App(...) { ... };

하지만 이렇게 작성할 경우 _app.tsx에 존재하는 다른 비즈니스 로직과 뒤섞여, _app.tsx파일에 복잡도가 높아질 수 있습니다.

따라서, src 폴더에 mocks 폴더를 만들고, MSW 관련 파일을 관리하면, _app.tsxMSW 로직의 관심사 분리가 가능합니다.


// src/mocks/browser.ts
import { setupWorker } from "msw";

export const worker = setupWorker([핸들러작성]);

// src/mocks/server.ts
import { setupServer } from "msw/node";

export const server = setupServer([핸들러작성]);

// src/mocks/index.ts
async function initMSW() {
    if (typeof window === "undefined") {
        const { server } = await import("./server");

        // 노드 환경에서 사용하는 Mock Server 옵션 추가
        server.listen(필요한옵션);
    } else {
        const { worker } = await import("./browser");
        
        // Service Worker Mocking 옵션 추가
        worker.start(필요한옵션);
    }
}

export { initMSW };

위와같이 작업한 이후 _app.tsx에서 아래와 같이 initMSW()만 호출시키면 관심사분리가 가능합니다.


// src/pages/_app.tsx
...
initMSW();
...
function App(...) { ... };

TIP 2 - MSW는 개발단계에서만 실행하도록 합니다.

MSW는 개발단계에서만 사용해야하므로 NODE_ENV 혹은 dotenv등을 이용해서 개발모드를 판단해주는게 좋습니다.


ex)

// src/pages/_app.tsx
...
if (process.env.NODE_ENV !== "production") {
    initMSW();
}
...
function App(...) { ... };

위와같이 처리를 하면 테스트단계나 개발단계에서는 MSW가 적용되지만, 그외에는 MSW가 적용이 되지 않습니다.

TIP 3 - 핸들러 파일도 분리하여 관리합니다.

API 핸들러를 작성하다보면 꽤 많은 핸들러가 생기게되고, 관리가 필요하게 됩니다.

따라서 핸들러 코드 또한 MSW 코어 파일들과 분리를 하는게 좋습니다.


// src/mocks/server.ts
import { setupServer } from "msw/node";

export const server = setupServer([핸들러작성]);

기존에는, 위 코드같이 setupServer안에 배열로 핸들러를 넘겨주어 코어파일의 복잡도가 상당히 높아졌습니다.

그렇기에, 아래 코드들처럼 핸들러 파일과 코어파일을 분리해주면, 코어파일의 복잡도가 낮아지고 자주 수정해야하는 핸들러 파일이 분리가 가능해집니다.


// src/mocks/handlers.ts
import type { DefaultBodyType, MockedRequest, RestHandler } from "msw";
import commonHandlers from "@/__mocks__/common.handler.ts";
import productHandlers from "@/__mocks__/product.handler.ts";

let handlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [...commonHandlers, ...productHandlers];

export default handlers;

handlers.ts파일은 모든 핸들러 코드를 하나의 핸들러로 종합하는 파일로서, setupServersetupWorker에 핸들러를 등록할 때, 넘겨주는 핸들러 배열입니다.


// src/mocks/server.ts
import { setupServer } from "msw/node";
import handlers from "./handlers";

export const server = setupServer(...handlers);

// src/mocks/browser.ts
import { setupWorker } from "msw";
import handlers from "./handlers";

export const worker = setupWorker(...handlers);

server.tsbrowser.ts파일에서 각각 handlers.ts에 있는 배열을 등록시켜주면, server.ts파일과 browser.ts파일을 건들지 않아도 핸들러를 등록할 수 있게 됩니다.

TIP 4 - 핸들러 폴더구조는 API주소를 기반으로 구성합니다

Mock API를 만들다 보면, 사용하는 API갯수만큼 핸들러도 늘어나게 되는데, 이러한 API들로인해 나중에는 찾고자하는 API를 찾기가 쉽지 않을정도로 많아질 수 있습니다.

따라서, 최대한 API의 주소를 따라가는게 좋습니다.

예를들어, http://localhost/test/verification이라는 API를 Mocking한다고 했을 때, 파일구조는 아래와 같이 작성됩니다.


...
└── __mocks__/
    ├── ...
    ├── localhost/
    │   └── test/
    │       └── verification
    └── ...
...

마무리

꽤 긴 글이었지만, 제 글을 끝까지 읽어주셔서 감사합니다(꾸벅(--)(__))

올리브영 프론트엔드파트에서는 MSW를 사용하여 많은 프로젝트를 수행하고 있으며,

MSW를 적용 후, 사용하기 전의 겪언던 문제들과 운영에서 발생하던 휴먼에러 등 다양한 문제를 해결하였습니다.

만약, 프로덕트를 운영하시면서 MSW를 사용하지 않으시는 프론트엔드 개발자 분이 있다면, 꼭 사용해보시라고 권유하고 싶습니다.

(츄라이~ 츄라이~)

만약 저 처럼 프로덕트가 가지고있는 문제점에 대해서 어떻게하면 문제를 해결할 수 있을지 고민하며 같이 성장하고 싶으신가요??

그렇다면 주저하지말고 올리브영으로 지원해주세요!

Right Now!!🫵



finish

FrontEndNEXT.JSMSW
올리브영 테크 블로그 작성 Next.js에서 MSW(Mock Service Worker)로 네트워크 Mocking하기
🐶
개발새발자 |
Front-end Engineer
개발새발 코딩하며 성장하려고 하는 프론트엔드 개발자입니다. 만반잘부~👋