Unit Test | 4.3. 상태 관리 모킹하기

Kate Jung·2024년 2월 27일
0

Front-end Test

목록 보기
16/17
post-thumbnail
post-custom-banner

통합 테스트는 주로 API를 통해 가져온 데이터나 앱에서 사용하는 상태를 기준으로 비즈니스 로직을 검증함.

📌 cart.js

// 장바구니 상품 정보는 로그인한 사용자와 매핑
// 앱 전반적으로 필요한 데이터이기 때문에 zustand를 사용한 상태 관리가 필요
export const useCartStore = create(set => ({
  cart: {},
  totalCount: 0,
  totalPrice: 0,
  // 편의상 로컬 스토리지를 사용하여 장바구니 상품 저장
  initCart: userId =>
    set(state => {
      ...생략
    }),
  resetCart: userId =>
    set(() => {
      ...생략
    }),
  addCartItem: (item, userId, count) =>
    set(state => {
      ...생략
    }),
  removeCartItem: (itemId, userId) =>
    set(state => {
      ...생략
    }),
  changeCartItemCount: ({ itemId, count, userId }) =>
    set(state => {
      ...생략
    }),
}));
  • 파일 설명

    • state로 관리하는 것 : 장바구니에 담긴 상품에 대한 정보와 총 수량, 가격
    • 액션 : 액션(ex. addCartItem, removeCartItem, resetCart 등)을 사용하여 장바구니의 상품을 추가 / 삭제 / 초기화 함.
  • 이렇게 생성된 스토어를 컴포넌트에서 직접 호출하여 필요한 state나 액션을 가져와 사용 가능

📌 PriceSummary.jsx

const PriceSummary = () => {
	...생략

	// useCartStore를 직접 호출하여 state (ex. TotalCount, TotalPrice)를 가져와 사용 가능
  const { totalCount, totalPrice } = useCartStore(state =>
    pick(state, 'totalPrice', 'totalCount'),
  );

	...생략

  return (
    생략
  );
};
  • zustand에 대한 자세한 사용법은 공식 문서에서 확인

📌 CartTable.jsx

  • 코드

    const CartTable = () => {
      return (
        <>
          <PageTitle /> {/* 장바구니 헤더 */}
          <ProductInfoTable /> {/* ✅ 상품 리스트 */}
          <Divider sx={{ padding: 2 }} />
          <PriceSummary /> {/* ✅ 가격 정보 */}
        </>
      );
    };
  • 체크(✅) 된 것만 통합 테스트 작성 예정

    • 이유

      • 나머지 컴포넌트들

        별도 로직 없이 단순히 타이틀 텍스트와 구분선만 렌더링하는 컴포넌트

      • 체크(✅) 된 컴포넌트들

        장바구니 state를 사용하여 데이터를 렌더링하는 중요한 컴포넌트이기 때문에 테스트로 state에 따라 UI가 올바르게 변경되는지 확인 필요

  • CartTable로 한 번에 통합 테스트를 작성하지 않는 이유

    • 너무 큰 범위로 통합 테스트를 작성하면 모킹해야 하는 정보가 너무 많아지고 작은 변형에도 깨지기 쉬워 테스트 코드 관리가 어려워짐

    • 두 컴포넌트는 서로 의존하지 않고 필요한 상태 정보만 따로 스토어에서 가져옴

      → 별도로 분리하여 독립적으로 테스트를 작성하기에 용이

  • ProductInfoTable, PriceSummary 컴포넌트는 zustand의 장바구니 스토어를 참조하여 여러가지 상태나 액션을 사용함.

    이러한 컴포넌트의 통합 테스트를 작성하기 위해서는 테스트 실행 전에 zustand 스토어의 상태를 원하는 대로 변경 가능해야 함.

    • ex

      장바구니 state에 특정 상품 정보를 미리 추가하여 해당 정보들이 제대로 렌더링 되는지 검증해야 함. 이를 위해서는 zustand 스토어를 모킹하여 원하는 대로 state 정보를 설정 가능해야 함.

📌 mocks/zustand.js

  • __mocks__ 폴더 하위에 위치한 파일

    • vitestjest에서 특정 모듈을 자동 모킹할 때 사용됨

    • 해당 폴더 하위에 모킹하고 싶은 모듈을 생성한 뒤 vi.mock 함수를 호출하여 원하는 모듈 이름을 지정하면 자동으로 모킹됨.

      예제에서는 setupTest.js 파일(테스트 환경에 글로벌 설정을 함)에 vi.mock('zustand')를 호출하여 zustand 모듈을 __mocks__ 하위에 있는 zustand.js 모듈로 모킹함.

  • 파일 코드

    const { create: actualCreate } = await vi.importActual('zustand');
    // 👆 실제 주스탠드 모듈을 가져옴
    import { act } from '@testing-library/react';
    
    // 앱에 선언된 모든 스토어에 대해 재설정 함수를 저장
    const storeResetFns = new Set();
    
    // 스토어를 생성할 때 초기 상태를 가져와 리셋 함수를 생성하고 set에 추가
    export const create = createState => {
      const store = actualCreate(createState); 
    	// 👆 스토어 생성
      const initialState = store.getState(); 
    	// 👆 스토어의 초기 상태, initialState를 기준으로
      storeResetFns.add(() => store.setState(initialState, true));
    	// 👆 스토어의 state를 초기화하는 코드를 set에 등록
      return store;
    };
    
    // 테스트가 구동되기 전 모든 스토어를 리셋
    // - 스토어 데이터를 initialState로 초기화하는 reset 함수를 실행하여 모든 데이터를 초기화
    // - 테스트의 독립성 유지 (모킹한 스토어 상태를 매번 초기화하여 이전의 모킹 결과가 다른 테스트에 영향을 미치지 않도록 함)
    beforeEach(() => {
      act(() => storeResetFns.forEach(resetFn => resetFn()));
    });

📌 mockZustandStore.jsx

테스트 실행 전 스토어의 상태를 변경하고 싶을 때

  • src/utils/test/mockZustandStore.jsx

    import { useCartStore } from '@/store/cart';
    import { useFilterStore } from '@/store/filter';
    import { useUserStore } from '@/store/user';
    
    // 이 함수는 적용하고자 하는 state를 인자로 받아 
    // 기존의 initState와 병합하는 작업을 함
    const mockStore = (hook, state) => {
      const initStore = hook.getState();
      hook.setState({ ...initStore, ...state }, true);
    };
    
    // mockStore 함수를 사용하면 zustand 스토어(ex. User, Cart, Filter)의 초기
    // 상태와 특정 스테이트를 병합하여 원하는 데이터로 스토어 스테이트를 변경 가능
    export const mockUseUserStore = state => {
      mockStore(useUserStore, state);
    };
    
    export const mockUseCartStore = state => {
      mockStore(useCartStore, state);
    };
    
    export const mockUseFilterStore = state => {
      mockStore(useFilterStore, state);
    };
  • 이 유틸 함수들을 사용하여 테스트 실행 전 원하는 형태로 스토어 데이터를 맞추어 테스트 실행 가능

  • 이 유틸 함수와 앞서 살펴본 zustand mocking 모듈을 같이 사용하면 테스트 환경에서 스토어의 스테이트 값을 편리하게 변경 가능

    또한 독립적으로 테스트를 실행 가능 (이유 : 테스트 구동 전, 자동으로 모든 스토어를 이니셜 스테이트로 초기화 해주기 때문)

📌 정리

  • 통합 테스트가 검증하는 것

    주로 API를 통해 가져온 데이터 or 앱에서 사용하는 상태를 기준으로 비즈니스 로직을 검증. 그러므로 상태관리 로직을 호출 or state를 사용하는 컴포넌트 역시 통합 테스트의 대상이 됨. (예제에서의 상태관리 로직 → 주스탠드 사용)

  • 특정 상황을 만들어 통합 테스트를 하기 위해서는(ex. 장바구니에 상품이 담긴 상태) 주스탠드에 대한 모킹이 필요.

    이러한 모킹은 테스트 전에 이루어져야 함.

profile
복습 목적 블로그 입니다.
post-custom-banner

0개의 댓글