
테스트코드 는 실제 서비스에서 발생할 수 있는 에러나, 유지보수, 신규 개발에서 발생할 수 있는 에러를 사전에 방지할 수 있습니다. [React / TS 도메인 로직 테스트 코드] 게시글을 읽고오시면 FrontEnd 에서 비즈니스 로직의 테스트코드가 어떤 효과를 줄 수 있는지 확인할 수 있습니다.
오늘은 실무에서 많이 사용하는 react-query 는 어떻게 테스팅 할 수 있고, 어떻게 목업 설정을 할 수 있는지 테스트코드 작성의 실제 예제를 확인해보려고 합니다. 따라서 react-query 를 안다는 가정하에 설치법, 세팅(provider)등에 대한 내용은 생략하도록 하겠습니다.
프론트 퍼블리싱
"use client"; import Image from "next/image"; import photoQuery from "@/apis/query/photos.query"; const PhotoItem = ({ photo }: { photo: Photo }) => { return ( <div data-testid="photo"> <Image src={photo.download_url} alt={photo.url} width={300} height={300} /> <span>{photo.author}</span> </div> ); }; const Photos = () => { const { data: photos } = photoQuery.getRandomPhotosQuery(); return ( <div className="max-w-3xl grid grid-cols-3 gap-3"> {photos?.map((photo) => ( <PhotoItem key={photo.id} photo={photo} /> ))} </div> ); }; export default Photos;
우리는 위와 같은 퍼블리싱 코드를 하나 작성했습니다. 위의 코드가 보여줄 화면은 매우 간단하게 화면에 api fetch를 통해서 받은 사진 리스트를 grid 형태로 보여주게 될 것입니다.
여기서 api fetch 받는 부분을 react-query 를 통해 캐싱하려고 합니다. (ps. 위와 같은 방식은 react에서 주로 사용하며 next.js에서도 간간히 나오는 패턴이지만 next.js에서는 잘 사용하지는 않습니다.)
우리는 실질적인 api fetch를 수행하는 함수 하나와 api fetch 받은걸 cache하기 위한 react-query 함수 이렇게 2가지 레어어를 구성할 것입니다.
API, Query 로직 구현
// photos.api.ts import { setSearchParams } from "@/utils/setSearchParams"; const baseUrl = "https://picsum.photos/v2"; const getRandomPhotos = async (page: string = "1", limit: string = "10"): Promise<Photo[]> => { const params = { page, limit }; const url = setSearchParams(`${baseUrl}/list`, params); const response = await fetch(url); return response.json(); }; export default { getRandomPhotos, }; // photos.query.ts import { UseQueryResult, useQuery } from "react-query"; import photoApi from "../photos.api"; const getRandomPhotosQuery = (page: string = "1", limit: string = "10"): UseQueryResult<Photo[]> => { return useQuery({ queryKey: ["photos", page, limit], queryFn: async () => photoApi.getRandomPhotos(), refetchOnWindowFocus: true, }); }; export default { getRandomPhotosQuery };
위 처럼 구성하면 우리는 qeuryFn 에서 fetch 받은 것을 토대로 정제할 수 있습니다. 주로 비즈니스 로직이 될 수 있죠!
여기서 주는 return 값은 useQueryResult로 isLoading, isFetching, data, refetch등을 포함하고 있고 우리는 오늘 여기서 data에 해당하는 값을 테스팅할 것입니다.
photos.query.ts
를 통해서 query 함수를 만들었으니 실 코드에서는 photoQuery.getRandomPhotosQuery() 를 통해 호출할 수 있습니다.호출받은 데이터를 바탕으로 그린 화면은 아래와 같습니다.

정확히 3개의 같은 크기 그리드가 생성되었고, 이미지가 보여지는 것을 확인할 수 있습니다.
우리가 테스트 할 내용은 총 3가지입니다.
- 간단한 타이틀 테스트
- React-Query의 로딩 상태 목업
- React-Query의 실제 Data 목업
Let’s Go Test!
Test Provider 세팅
const renderProviderComponent = ({ children, client, }: { children: JSX.Element; client?: QueryClient; }): React.ReactElement => { return ( <QueryClientProvider client={client ?? new QueryClient()}>{children}</QueryClientProvider> ) }
react-query 를 사용하려면 provider로 감싸져야 사용할 수 있으므로 테스트 환경도 동일하게 세팅해줍니다. client 를 외부에서 주입시켜주는데, 굳이 react-query를 테스트할 계획이 아니라면 넘길 수 있습니다.
이제 환경 설정은 끝이났고, 테스트환경에 사용할 mockData를 하나 만들어야 합니다.
MockData 만들기
const mockData = [ { id: "0", author: "Alejandro Escamilla", width: 5000, height: 3333, url: "https://unsplash.com/photos/yC-Yzbqy7PY", download_url: "https://picsum.photos/id/0/5000/3333", }, ...생략 { id: "9", author: "Alejandro Escamilla", width: 5000, height: 3269, url: "https://unsplash.com/photos/ABDTiLqDhJA", download_url: "https://picsum.photos/id/9/5000/3269", }, ];
MockData는 실제로 PhotosQuery 가 return 해주는 타입과 동일하게 구현해야 합니다. 만약, 테스트하는 대상이 특정 key(id, author 등)만 사용한다면 사용하는 키만 넣고 타입을 강제로 지정해줘도 무방합니다.
React-Query 테스트하기
describe("<Home /> 테스트", () => { // Test1 it("타이틀 테스트", () => { const { getByRole } = render(renderProviderComponent({ children: <Home /> })); const title = getByRole("heading", { level: 2, }); expect(title).toHaveTextContent("랜덤 사진"); }); // Test2 it("로딩 상태 확인", () => { photoQuery.getRandomPhotosQuery = jest.fn().mockReturnValue({ isLoading: true, }); const client = new QueryClient(); const { getByText } = render(renderProviderComponent({ children: <Home />, client })); expect(getByText("loading")).toBeInTheDocument(); }); // Test3 it("10개가 맞는지.", () => { photoQuery.getRandomPhotosQuery = jest.fn().mockReturnValue({ data: mockData, }); const client = new QueryClient(); const { getAllByTestId } = render(renderProviderComponent({ children: <Home />, client })); expect(getAllByTestId("photo")).toHaveLength(mockData.length); }); });
Test1은 간단하게 타이틀을 체크합니다. UI 테스트도 중요하지만, UI의 경우 요구사항에 따라 자주 바뀌곤 합니다. 만약 비즈니스 로직과 뷰 로직, UI 중에 시간에 따라 테스트 우선순위를 지정해야한다면 비즈니스 로직 테스트에 많은 시간을 사용하는 것을 추천합니다.
Test2는 로딩 상태를 확인합니다. 저기서 photoQuery.getRandomPhotosQuery 는 뭘까요? 우리가 위에서 지정한 react-query 의 useQuery 를 return 해주는 함수입니다. 우리는 저 photoQuery.getRandomPhotosQuery 에 jest.fn의 mockReturnValue를 통해 목업 데이터를 넣어줄 겁니다.
만약 저렇게 export default로 module를 export 하는게 아니라 fn를 return 하는 경우라면 jest.fn(function)을 통해서도 목업 데이터를 주입할 수 있습니다.
Test2에 isLoading을 넣으면 맨 위에서 작성한 UI는 loading이라는 text를 screen에 보여주게 될 것입니다.
Test3은 실질적인 data를 테스트합니다. 위에서 작성한 MockData를 returnValue에 넣어서 data를 모킹할 수 있습니다. 예제에서는 10개의 MockData를 주입해주었고, testid를 통해 총 몇개가 렌더링 되어있는지를 체크하였습니다.

모든 테스트가 성공적으로 통과한 것을 확인할 수 있습니다. 사실 함수를 mocking 하는 방식은 여러가지가 있습니다. mockReturnValue 말고 mockReturnValueOnce 를 사용할 수도 있습니다.
아직 모든 mock 함수를 알지 못해서 하나하나 공부해가며 블로그에 포스팅 하도록 하겠습니다.
위 예제 코드는 깃허브 에 올려두었습니다! 참고할만한 내용까진 아닐 것 같지만 그래도 혹시 궁금하시다면 한번씩 보고 피드백 남겨주세요!