Vitest로 테스트하기(2) - 통합테스트

신태일·2024년 10월 17일
post-thumbnail

💡 통합 테스트에서는

✔️ 여러 모듈이 조합되었을 때 비즈니스 로직을 검증
✔️ 비즈니스 로직 기준으로 여러 컴포넌트들 간의 상호 작용을 한 번에 검증할 수 있음

작성해야 할 통합 테스트로는..

상태나 데이터를 관리하는 특정 컴포넌트를 기준으로 하위 컴포넌트가 제대로 렌더링 되는지 검증하는 테스트

데이터를 관리하는 로직이 산재 → 통합 테스트 작성이 어려움
앱의 상태를 어디서 어떻게 관리하고 변경할지 구조적인 설계가 중요함


통합 테스트 항목


✔️ 특정 상태를 기준으로 동작하는 컴포넌트 조합

✔️ API와 함께 상호작용 하는 컴포넌트 조합

⇒ 단순 UI 렌더링 및 간단한 로직을 실행하는 컴포넌트까지 한번에 효율적으로 검증 가능


통합 테스트의 장점

  • 여러 개의 모듈이 동시에 상호 작용 하는 것을 테스트하기 때문에 단위 테스트에 비해 모킹의 비중이 적으며, 모듈 간에 발생하는 에러를 검증할 수 있다.
  • 실제 앱이 동작하는 비즈니스 로직에 가깝게 기능을 검증할 수 있다.
  • 하위 모듈의 단위 테스트에서 검증할 수 있는 부분까지 한 번에 효율적으로 검증할 수 있다.

결국 중요한 건

  • 컴포넌트의 상태 및 데이터를 어디서 관리하고 변경할 지 구조적인 설계가 잘 되어야 함.
  • 좋은 설계를 위한 매개체가 된다!

쇼핑몰 예제 프로젝트의 통합 테스트 전략

  • 통합 테스트에서는 구성된 비즈니스 로직을 적절한 단위로 나눠 컴포넌트 집합을 검증해야 한다.
  • 비즈니스 로직을 기준으로 통합 테스트를 나눌 때는
    • 가능한 한 모킹을 하지 않고 실제와 유사하게 검증한다.
    • 비즈니스 로직을 처리하는 상태 관리나 API 호출은 상위 컴포넌트로 응집한다.
    • 변경 가능성을 고려해 여러 도메인의 기능이 조합된 비즈니스 로직은 나눠 검증한다.
  • 메인 페이지는 네비게이션 바, 상품 검색, 상품 리스트 영역으로 나눠 테스트를 작성한다.
  • 장바구니 페이지는 상품 리스트, 가격 계산 영역으로 나눠 테스트를 작성한다.
  • 이를 통해 컴포넌트간 결합도를 낮추고 견고한 설계를 통해 유지 보수하기 좋은 코드를 작성할 수 있다.

상태 관리 모킹하기


상태 관리와 통합 테스트

  • 예제에서는 zustand를 사용하여 앱의 상태를 관리
  • 원하는 상태로 통합 테스트를 하기 위해 zustand 모킹 필요
    • ex) 장바구니에 상품이 담긴 상태

앱의 전역 상태를 모킹해 테스트 전, 후에 값을 변경하고 초기화해야 한다.

  • mocks/zustand.js를 통해 자동 모킹을 적용해 스토어를 초기화하자
  • mockZustandStore의 유틸 함수를 통한 zustand 스토어의 상태 변경
  • React Redux나 Recoil과 같은 상태 관리 라이브러리도 모킹 가이드를 제공

통합 테스트 작성하기 - 상태 관리 모킹


ProductInfoTable 통합 테스트

  • cart, user 스토어 + ProductInfoTableRow(MUI)가 결합된 컴포넌트
  • ProductInfoTable과 같은 컴포넌트에 state와 API에 대한 제어 코드를 응집해 관리하는 것이 좋다.
    • 로직 파악 및 유지 보수에 좋으며, 통합 테스트의 단위를 깔끔하게 나눌 수 있다.
  • 비즈니스 로직을 실제 사용성과 유사하도록 사용자 인터렉션을 통해 컴포넌트 통합 테스트로 검증할 수 있다.

msw로 API 모킹하기

  • 통합 테스트에서 API를 호출하는 컴포넌트를 시뮬레이션 하기 위해서는 프로젝트에서 사용하는 탠스택 쿼리 설정과 API에 대한 모킹이 필요하다!

탠스택 쿼리

  • API 호출에 따른 로딩, 에러 상태 처리 및 페이지네이션 캐싱 등의 편의성을 위해 사용
  • 기존 설정(ex> retry)이 테스트에 영향을 주지 않도록 설정(render.jsx) 수정
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    import { render } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import React from 'react';
    import { Toaster } from 'react-hot-toast';
    import { MemoryRouter } from 'react-router-dom';
    
    // https://tanstack.com/query/v4/docs/react/guides/testing
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          // ✅ turns retries off
          // 기존 탠스택 쿼리에는 요청 실패시 최대 3번의 추가 요청을 하게 되는데
          // 테스트 시에는 실패시 괜히 테스트 시간이 길어져 원하는 에러 메시지를 못 볼 수 있다.
          retry: false,
        },
      },
      logger: {
        log: console.log,
        warn: console.warn,
        // ✅ no more errors on the console for tests
        error: process.env.NODE_ENV === 'test' ? () => {} : console.error,
      },
    });
    
    export default async (component, options = {}) => {
      const { routerProps } = options;
      const user = userEvent.setup();
    
      return {
        user,
        ...render(
          <QueryClientProvider client={queryClient}>
            <MemoryRouter {...routerProps}>{component}</MemoryRouter>
            <Toaster />
          </QueryClientProvider>,
        ),
      };
    };
    

Mock Service Worker(MSW)

  • Node.js 브라우저 환경을 위한 API 모킹 라이브러리
  • 브라우저는 서비스 워커를 사용, Node.js 환경에서는 내부적으로 XHR, fetch 등의 요청을 가로채는 인터셉터를 사용
  • setup과 teardown을 사용해 테스트 실행 전, 후에 API를 모킹하고 해제
    // handlers.js
    // API의 응답 모킹을 위해 Request Handler 라는 것을 정의해서 사용한다.
    
    import { rest } from 'msw';
    
    import response from '@/__mocks__/response';
    import { apiRoutes } from '@/apiRoutes';
    
    const API_DOMAIN = 'http://localhost:3000';
    
    export const handlers = [
      ...[
        apiRoutes.users,
        apiRoutes.product,
        apiRoutes.categories,
        apiRoutes.couponList,
      ].map(path =>
      .
      .
      .
      
    // setupTest.js
    // handlers.js에서 작성된 응답 모킹을 테스트 서버와 연결한다.
    // setup, teardown을 통해 테스트 서버를 상황에 맞게 작동시킨다.
    import { setupServer } from 'msw/node';
    import '@testing-library/jest-dom';
    
    import { handlers } from '@/__mocks__/handlers';
    
    /* msw */
    export const server = setupServer(...handlers);
    // msw 설정 적용
    // -> 테스트 환경에서 API 호출은 msw의 핸들러에 설정한 응답으로 모킹
    beforeAll(() => {
      // 서버 구동
      server.listen();
    });
    
    afterEach(() => {
      server.resetHandlers();
      vi.clearAllMocks();
    });
    
    afterAll(() => {
      vi.resetAllMocks();
      server.close();
    });
    
profile
노원거인

0개의 댓글