애드블럭 종료 후 보실 수 있습니다.

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 실무에서 유용한 MSW(Mock Service Worker) 활용 가이드
    Tip 2024. 11. 30. 16:54
    728x90
    반응형

    MSW(Mock Service Worker)

     

    프런트엔드 개발을 하다 보면 백엔드 API가 완성되지 않은 상태에서 작업해야 하는 API 의존성 문제는 항상 개발 속도와 품질에 큰 영향을 미칩니다.

    이런 상황에서 모킹(Mocking)은 필수적이지만, 기존 방식에는 한계가 있습니다.

    오늘은 개발 생산성을 높여주는 MSW(Mock Service Worker)에 대해 깊이 있게 알아보겠습니다.

    1. 기존 모킹 방식의 한계

    1. 모듈 단위의 모킹

    Jest와 같은 테스트 프레임워크에서 jest.mock()을 사용하여 모듈을 모킹 하는 것이 일반적입니다.

    하지만 이 방법은 함수나 모듈 단위로만 모킹이 가능하며, 실제 네트워크 요청을 대체하지는 않습니다.

    2. 통합 테스트의 어려움

    엔드투엔드(E2E) 테스트나 통합 테스트에서는 실제 API 서버와의 연동이 필요합니다.

    하지만 백엔드가 완성되지 않았거나 테스트 환경을 구축하기 어려운 경우 테스트 작성이 어렵습니다.

    3. 유지보수의 복잡성

    모킹 코드가 애플리케이션 코드와 분리되어 있지 않으면, 유지보수가 어려워지고 코드베이스가 복잡해집니다.

    2. MSW란 무엇인가?

    MSW는 API 호출을 가로채서 응답을 제어할 수 있도록 해주는 모킹 도구로, 실제 API 서버 없이도 요청에 대한 응답을 정의하고 테스트할 수 있도록 도와줍니다.

    브라우저 환경에서는 Service Worker로, Node.js 환경에서는 네이티브 핸들러로 동작합니다.

    이러한 특성 덕분에 MSW는 사용자와 서버 간의 상호작용을 완벽하게 모방할 수 있어 실제 환경에 가까운 테스트를 가능하게 합니다.

    주요 특징

    • 네트워크 레벨에서의 모킹: HTTP 요청 자체를 가로채므로, 실제 환경과 유사한 테스트가 가능합니다.
    • 브라우저와 Node.js 환경 지원: 프런트엔드 개발과 테스트 모두에 활용할 수 있습니다.
    • 타입 안전성: TypeScript 지원으로 타입 안전한 코드를 작성할 수 있습니다.
    • 개발 워크플로와의 통합: 개발 서버, 스토리북, 테스트 환경 등과 쉽게 통합 가능합니다.

    3. MSW의 장점

    1. 개발 생산성 향상

    • 백엔드 API 개발 완료를 기다리지 않고 즉시 프런트엔드 개발 착수 가능
    • 병렬적인 개발 프로세스로 인한 프로젝트 일정 단축
    • API 스펙 변경 시 즉각적인 대응 가능

    2. 테스트 안정성 강화

    • 네트워크 불안정성에 영향받지 않는 일관된 테스트 환경 제공
    • E2E 테스트의 신뢰성 향상
    • 다양한 에지 케이스 시나리오 테스트 용이

    3. 비용 효율성

    • 테스트 환경 구축 및 유지보수 비용 감소
    • 개발 서버 리소스 절약
    • QA 프로세스 효율화

    4. MSW 설치 및 기본 사용법

    1. MSW 설치

    npm install msw --save-dev

    2. Service Worker 설정 파일 생성

    프로젝트의 src 디렉터리에 mocks 폴더를 만들고 browser.js 파일을 생성합니다.

    // src/mocks/browser.ts
    import { setupWorker } from 'msw';
    import { handlers } from './handlers';
    
    export const worker = setupWorker(...handlers);

    3. 요청 핸들러 정의

    handlers.js 파일에서 요청을 가로챌 엔드포인트와 응답을 정의합니다.

    // src/models/User.ts
    export interface User {
      id: number;
      username: string;
      email: string;
    }
    
    // src/mocks/handlers.ts
    import { rest } from 'msw';
    import { User } from '../models/User'; // 예시로 User 타입을 사용
    
    export const handlers = [
      rest.get('/api/user', (req, res, ctx) => {
        const user: User = {
          id: 1,
          username: '홍길동',
          email: 'hong@gildong.com',
        };
        return res(ctx.status(200), ctx.json(user));
      }),
    ];

    4. 개발 환경에서 MSW 실행

    4.1 Next.js Page Router

    // pages/_app.tsx
    import { AppProps } from 'next/app';
    
    if (typeof window !== 'undefined') {
      const { worker } = require('../src/mocks/browser');
      worker.start();
    }
    
    function MyApp({ Component, pageProps }: AppProps) {
      return <Component {...pageProps} />;
    }
    
    export default MyApp;

     

    4.2 Next.js App Router

    // src/app/GlobalError.tsx
    'use client';
    
    import { useEffect } from 'react';
    
    export function MSWInitializer() {
      useEffect(() => {
        if (process.env.NODE_ENV === 'development') {
          const { worker } = require('../mocks/browser');
          worker.start();
        }
      }, []);
    
      return null;
    }
    
    // src/app/layout.tsx
    import { MSWInitializer } from './GlobalError';
    
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="ko">
          <body>
            <MSWInitializer />
            {children}
          </body>
        </html>
      );
    }

    5. 테스트 환경에서 MSW 실행

    // Jest 설정 파일
    // src/setupTests.ts
    import { server } from './mocks/server';
    
    // 모든 테스트 전에 서버를 시작합니다.
    beforeAll(() => server.listen());
    
    // 각 테스트 후에 핸들러를 리셋합니다.
    afterEach(() => server.resetHandlers());
    
    // 모든 테스트가 끝나면 서버를 닫습니다.
    afterAll(() => server.close());
    
    // Node.js용 서버 설정
    // src/mocks/server.ts
    import { setupServer } from 'msw/node';
    import { handlers } from './handlers';
    
    // 서버를 설정하고 핸들러를 적용합니다.
    export const server = setupServer(...handlers);

    5. MSW의 고급 활용 전략

    1. 동적 모킹

    실무에서는 단순히 정적인 응답을 반환하는 것을 넘어, 요청의 파라미터나 바디에 따라 동적으로 응답을 생성해야 할 때가 있습니다.

    MSW는 이를 쉽게 구현할 수 있습니다:

    // src/mocks/handlers.ts
    import { rest } from 'msw';
    import { User } from '../models/User';
    
    export const handlers = [
      rest.get('/api/user/:userId', (req, res, ctx) => {
        const { userId } = req.params as { userId: string };
        const user: User = {
          id: parseInt(userId, 10),
          username: `사용자_${userId}`,
          email: `user${userId}@example.com`,
        };
        return res(ctx.status(200), ctx.json(user));
      }),
    ];

    2. 네트워크 상태 시뮬레이션

    실제 네트워크 환경을 시뮬레이션하기 위해 지연, 에러, 타임아웃 등을 구현할 수 있습니다.

    // src/mocks/handlers.ts
    import { rest } from 'msw';
    
    rest.get('/api/data', (req, res, ctx) => {
      return res(
        ctx.delay(2000), // 2초 지연
        ctx.status(500), // 서버 에러
        ctx.json({ message: 'Internal Server Error' })
      );
    })

     

    3. API 모킹 설계 패턴

    // handlers/api.ts
    import { rest } from 'msw'
    import { createResponseComposer } from './utils'
    
    const compose = createResponseComposer()
    
    export const handlers = [
      rest.get('/api/users', (req, res, ctx) => {
        return compose(ctx, {
          success: () => ({
            users: mockUsers,
            metadata: { total: 100 }
          }),
          error: {
            401: '인증이 필요합니다',
            403: '접근 권한이 없습니다'
          }
        })
      })
    ]

    4. 실제 API와의 전환 전략

    // config/api.ts
    const API_CONFIG = {
      useMock: process.env.REACT_APP_USE_MOCK === 'true',
      mockDelay: process.env.REACT_APP_MOCK_DELAY || 500,
      mockFailureRate: process.env.REACT_APP_MOCK_FAILURE_RATE || 0
    }
    
    export const initializeMSW = async () => {
      if (API_CONFIG.useMock) {
        const { worker } = await import('../mocks/browser')
        return worker.start({
          serviceWorker: {
            url: '/mockServiceWorker.js'
          },
          waitUntilReady: true,
        })
      }
    }

    5. 데이터 일관성 관리

    // mocks/db.ts
    import { factory, primaryKey } from '@mswjs/data'
    
    export const db = factory({
      user: {
        id: primaryKey(String),
        name: String,
        email: String,
        role: String
      },
      post: {
        id: primaryKey(String),
        title: String,
        content: String,
        authorId: String
      }
    })
    
    // 관계 설정
    export const getPostWithAuthor = (postId: string) => {
      const post = db.post.findFirst({
        where: { id: { equals: postId } }
      })
      const author = post ? db.user.findFirst({
        where: { id: { equals: post.authorId } }
      }) : null
      
      return { ...post, author }
    }

    6. Web Socket 모킹

    // handlers/websocket.ts
    import { setupWorker, rest } from 'msw'
    
    let connections = new Set()
    
    export const websocketHandlers = [
      rest.get('/ws', (req, res, ctx) => {
        const connection = new WebSocket('mock://ws')
        connections.add(connection)
        
        connection.onmessage = (event) => {
          // 실시간 메시지 처리 로직
        }
        
        return res(ctx.status(101)) // Switching Protocols
      })
    ]

    6. MSW 도입 시 고려사항

    • 보안
      • 민감한 데이터를 모킹 데이터에 포함하지 않도록 주의
      • 프로덕션 빌드에 모킹 코드가 포함되지 않도록 설정
      • API 키나 인증 토큰의 안전한 관리
    • 팀 협업
      • API 스펙 문서와 모킹 데이터의 동기화 유지
      • 공통 모킹 데이터 저장소 관리
      • 모킹 규칙에 대한 팀 컨벤션 수립
    • 유지보수
      • 모듈화 된 핸들러 구조 설계
      • 실제 API 응답 구조와의 일관성 유지
      • 자동화된 타입 생성 및 관리
    • 성능
      • 대규모 애플리케이션에서 많은 수의 모의 핸들러를 사용할 경우, 초기 로딩 시간에 주의
      • 필요한 경우 코드 스플리팅을 고려

    MSW는 단순한 모킹 도구를 넘어 프런트엔드 개발 프로세스를 혁신적으로 변화시키는 강력한 도구입니다.

    백엔드와의 의존성을 줄이고, 테스트의 신뢰성을 높이며, 개발 생산성을 크게 향상할 수 있습니다.

    MSW의 도입은 팀의 개발 문화와 프로세스를 한 단계 업그레이드시킬 수 있는 좋은 기회라고 생각합니다.


    실무에서 MSW를 효과적으로 활용하기 위해서는 단순히 기술적인 측면뿐만 아니라, 팀 내 협업 방식의 변화와 테스트 전략의 재정립이 함께 이루어져야 합니다.

    MSW를 통해 프런트엔드 개발자들이 더 자율적이고 생산적으로 일할 수 있는 환경을 만들어갈 수 있을 것입니다.

     

    참고 자료

    반응형

    댓글

Designed by Tistory.