[TanStakQuery] Testing

Jeris·2023년 5월 22일
0

React Query는 제공되는 훅 또는 이를 감싸는 커스텀 훅을 통해 작동합니다.

React 17 이하 버전에서는 이러한 커스텀 훅에 대한 유닛 테스트를 React Hooks Testing Library 라이브러리를 사용하여 작성할 수 있습니다.

이것을 실행하여 설치하세요:

npm install @testing-library/react-hooks react-test-renderer --save-dev

(react-test-renderer 라이브러리는 @testing-library/react-hooks의 peer dependency로 필요하며 사용 중인 React 버전과 일치해야 합니다.)

"참고: React 18 이상을 사용하는 경우 @testing-library/react 패키지를 통해 renderHook을 직접 사용할 수 있으며, @testing-library/react-hooks는 더 이상 필요하지 않습니다."

Our First Test

설치가 완료되면 간단한 테스트를 작성할 수 있습니다. 다음 커스텀 훅이 주어집니다:

export function useCustomHook() {
  return useQuery({ queryKey: ['customHook'], queryFn: () => 'Hello' });
}

React 17 이전 버전을 사용하면 다음과 같이 테스트를 작성할 수 있습니다:

const queryClient = new QueryClient();
const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>
    {children}
  </QueryClientProvider>
);

const { result, waitFor } = renderHook(() => useCustomHook(), { wrapper });

await waitFor(() => result.current.isSuccess);

expect(result.current.data).toEqual("Hello");

React 18 이상에서는 waitFor의 의미가 변경되었으므로 위의 테스트는 다음과 같이 수정해야 합니다:

import { renderHook, waitFor } from "@testing-library/react";

...

const { result } = renderHook(() => useCustomHook(), { wrapper });

await waitFor(() => expect(result.current.isSuccess).toBe(true));

React Query는 QueryClient, QueryClientProvider를 빌드하는 커스텀 래퍼를 제공한다는 점에 유의하세요. 이렇게 하면 테스트가 다른 테스트와 완전히 격리되도록 할 수 있습니다.

이 래퍼를 한 번만 작성할 수도 있지만, 그렇게 할 경우 모든 테스트 전에 QueryClient를 지우고 테스트가 병렬로 실행되지 않도록 해야 하며, 그렇지 않으면 한 테스트가 다른 테스트의 결과에 영향을 미칠 수 있습니다.

Turn off retries

라이브러리는 기본적으로 지수 백오프(exponential backoff)를 사용하여 세 번 재시도하도록 설정되어 있으므로 잘못된 쿼리를 테스트하려는 경우 테스트가 시간 초과될 수 있습니다. 재시도를 끄는 가장 쉬운 방법은 QueryClientProvider를 사용하는 것입니다. 위의 예제를 확장해 보겠습니다:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // ✅ turns retries off
      retry: false,
    },
  },
})
const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>
    {children}
  </QueryClientProvider>
);

이렇게 하면 컴포넌트 트리의 모든 쿼리에 대한 기본값이 "no retries"로 설정됩니다. 이 설정은 실제 useQuery에 명시적인 재시도가 설정되어 있지 않은 경우에만 작동한합니다. 5번의 재시도를 원하는 쿼리가 있는 경우 기본값은 대체(fallback)로만 사용되므로 이 쿼리가 여전히 우선합니다.

Turn off network error logging

When testing we want to suppress network errors being logged to the console. To do this, we can pass a custom logger to QueryClient:
테스트할 때 콘솔에 기록되는 네트워크 오류를 억제하고 싶을 수 있습니다. 이를 위해 커스텀 로거를 QueryClient에 전달하면 됩니다:

import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient({
  logger: {
    log: console.log,
    warn: console.warn,
    // ✅ no more errors on the console for tests
    error: process.env.NODE_ENV === 'test' ? () => {} : console.error,
  },
})

Set cacheTime to Infinity with Jest

Jest를 사용하는 경우 cacheTimeInfinity로 설정하여 "Jest did not exit one second after the test run completed" 오류 메시지를 방지할 수 있습니다. 이는 서버의 기본 동작이며, 명시적으로 cacheTime을 설정하는 경우에만 설정하면 됩니다.

Testing Network Calls

React Query의 주요 용도는 네트워크 요청을 캐시하는 것이므로, 코드가 올바른 네트워크 요청을 하고 있는지 테스트하는 것이 중요합니다.

이를 테스트할 수 있는 방법은 여러 가지가 있지만, 이 예제에서는 nock을 사용하겠습니다.

다음과 같은 커스텀 훅이 주어집니다:

function useFetchData() {
  return useQuery({
    queryKey: ['fetchData'],
    queryFn: () => request('/api/data'),
  });
}

다음과 같이 테스트를 작성할 수 있습니다:

const queryClient = new QueryClient();
const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>
    {children}
  </QueryClientProvider>
);

const expectation = nock('http://example.com')
  .get('/api/data')
  .reply(200, {
    answer: 42
  });

const { result, waitFor } = renderHook(() => useFetchData(), { wrapper });

await waitFor(() => {
  return result.current.isSuccess;
});

expect(result.current.data).toEqual({answer: 42});

여기서는 waitFor를 사용하고 쿼리 상태가 요청이 성공했음을 나타낼 때까지 기다리고 있습니다. 이렇게 하면 훅이 완료되었고 올바른 데이터를 가져야 한다는 것을 알 수 있습니다. 참고: React 18을 사용할 때는 위에서 언급한 것처럼 waitFor의 의미가 변경되었습니다.

Testing Load More / Infinite Scroll

먼저 API 응답을 모방(mock)해야 합니다.

function generateMockedResponse(page) {
  return {
    page: page,
    items: [...]
  }
}

그런 다음, nock은 다음 페이지에 따라 응답을 다르게 설정해야 하며, 이를 위해 uri를 사용해야 합니다. 여기서 uri의 값은 /?page=1 또는 /?page=2와 같은 값입니다.

const expectation = nock('http://example.com')
  .persist()
  .query(true)
  .get('/api/data')
  .reply(200, (uri) => {
    const url = new URL(`http://example.com${uri}`);
    const { page } = Object.fromEntries(url.searchParams);
    return generateMockedResponse(page);
  });

(이 엔드포인트에서 여러 번 호출할 것이므로 .persist()에 주목하세요).

이제 테스트를 안전하게 실행할 수 있습니다. 여기서 요령은 데이터 assertion이 통과될 때까지 await하는 것입니다:

const { result, waitFor } = renderHook(
  () => useInfiniteQueryCustomHook(),
  { wrapper },
);

await waitFor(() => result.current.isSuccess);

expect(result.current.data.pages).toStrictEqual(generateMockedResponse(1));

result.current.fetchNextPage();

await waitFor(() =>
  expect(result.current.data.pages).toStrictEqual([
    ...generateMockedResponse(1),
    ...generateMockedResponse(2),
  ]),
);

expectation.done();

참고: React 18을 사용할 때, 위에서 언급한 것처럼 waitFor의 의미가 변경되었습니다.

Further reading

추가 팁과 mock-service-worker를 사용한 대체 설정에 대해서는 커뮤니티 리소스에서 Testing React Query를 참조하세요.

Reference

profile
job's done

0개의 댓글