통합 테스트는 주로 API를 통해 가져온 데이터나 앱에서 사용하는 상태를 기준으로 비즈니스 로직을 검증함.
// 장바구니 상품 정보는 로그인한 사용자와 매핑
// 앱 전반적으로 필요한 데이터이기 때문에 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나 액션을 가져와 사용 가능
const PriceSummary = () => {
...생략
// useCartStore를 직접 호출하여 state (ex. TotalCount, TotalPrice)를 가져와 사용 가능
const { totalCount, totalPrice } = useCartStore(state =>
pick(state, 'totalPrice', 'totalCount'),
);
...생략
return (
생략
);
};
코드
const CartTable = () => {
return (
<>
<PageTitle /> {/* 장바구니 헤더 */}
<ProductInfoTable /> {/* ✅ 상품 리스트 */}
<Divider sx={{ padding: 2 }} />
<PriceSummary /> {/* ✅ 가격 정보 */}
</>
);
};
체크(✅) 된 것만 통합 테스트 작성 예정
이유
나머지 컴포넌트들
별도 로직 없이 단순히 타이틀 텍스트와 구분선만 렌더링하는 컴포넌트
체크(✅) 된 컴포넌트들
장바구니 state를 사용하여 데이터를 렌더링하는 중요한 컴포넌트이기 때문에 테스트로 state에 따라 UI가 올바르게 변경되는지 확인 필요
CartTable로 한 번에 통합 테스트를 작성하지 않는 이유
너무 큰 범위로 통합 테스트를 작성하면 모킹해야 하는 정보가 너무 많아지고 작은 변형에도 깨지기 쉬워 테스트 코드 관리가 어려워짐
두 컴포넌트는 서로 의존하지 않고 필요한 상태 정보만 따로 스토어에서 가져옴
→ 별도로 분리하여 독립적으로 테스트를 작성하기에 용이
ProductInfoTable
, PriceSummary
컴포넌트는 zustand의 장바구니 스토어를 참조하여 여러가지 상태나 액션을 사용함.
이러한 컴포넌트의 통합 테스트를 작성하기 위해서는 테스트 실행 전에 zustand 스토어의 상태를 원하는 대로 변경 가능해야 함.
ex
장바구니 state에 특정 상품 정보를 미리 추가하여 해당 정보들이 제대로 렌더링 되는지 검증해야 함. 이를 위해서는 zustand 스토어를 모킹하여 원하는 대로 state 정보를 설정 가능해야 함.
__mocks__
폴더 하위에 위치한 파일
vitest
나 jest
에서 특정 모듈을 자동 모킹할 때 사용됨
해당 폴더 하위에 모킹하고 싶은 모듈을 생성한 뒤 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()));
});
테스트 실행 전 스토어의 상태를 변경하고 싶을 때
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. 장바구니에 상품이 담긴 상태) 주스탠드에 대한 모킹이 필요.
이러한 모킹은 테스트 전에 이루어져야 함.