본문 바로가기

FrontEnd

리액트 쿼리 : 테스트 / 테스팅

반응형

Photo by   Girl with red hat

원문 보기 : https://tkdodo.eu/blog/testing-react-query

 

Testing React Query

Let's take a look at how to efficiently test custom useQuery hooks and components using them.

tkdodo.eu

순수하지 않은 컴포넌트

useContext, useSelector, useQuery를 사용하는 컴포넌트들은 순수하지 않습니다.

다른 환경에서 호출하면 다른 결과가 발생하기 때문입니다.

해당 컴포넌트들을 테스트 할 때에는 "의존성" 공급에 대해 생각해야 합니다.


네트워크 요청 흉내내기

 

React Query는 비동기 서버 상태 관리 라이브러리이므로

컴포넌트가 백엔드에 데이터를 요청할 가능성이 높습니다.
테스트 시에는 이 백엔드에 대한 의존성을 제거해야 합니다.
 
jest(농담)으로 데이터를 흉내내는 방법에 대한 기사가 많이 있습니다.
API 클라이언트, fetch 또는 axios를 직접 흉내낼 수 있습니다.

Kent C. Dodds가 Stop mocking fetch 기사에서 쓴 것을 다시 한 번 말합니다.

 

 

Use mock service worker by @ApiMocking

API를 흉내낼 때 단일 진실 원천이 될 수 있습니다.
  • 테스트 시 node 환경에서 동작합니다.
  • REST, GraphQL 모두 지원합니다.
  • storybook addon이 있으므로 useQuery를 사용하는 컴포넌트의 스토리를 작성할 수 있습니다.
  • 개발 환경의 브라우저에서 동작합니다. 즉, 브라우저 개발 도구의 네트워크 탭에서 요청을 볼 수 있습니다.
  • fixture(테스트 환경 설정 객체)와 함깨, cypress와 함께 동작합니다.
네트워크 계층이 처리되었으니, React Query 관련 사항에 대해 이야기를 시작할 수 있습니다.

QueryClientProvider

React Query를 사용할 때 QueryClientProvider가 필요하고,

QueryCache를 보유하는 껍데기인 queryClient를 제공해야 합니다. 캐시는 쿼리 각각의  데이터를 보유합니다.

 

각 테스트에 고유한 QueryClientProvider를 제공하고 각 테스트에 대해 새 QueryClient를 만드는 것을 선호합니다.

그렇게 하면 테스트가 서로 완전히 격리됩니다.

 
다른 접근 방식은 각 테스트 후에 캐시를 지우는 것일 수 있지만 테스트 간에 공유 상태를 가능한 한 최소화하는 것이 좋습니다.
그렇지 않으면 테스트를 병렬로 실행하면 예기치 않은 비정상적인 결과를 얻을 수 있습니다.

커스텀 훅 테스트

커스텀 hook 테스트 시, react-hooks-testing-library를 사용하면 hook을 테스트하기 쉽습니다.

렌더링 시 테스트 컴포넌트를 래핑하는 React 컴포넌트인 wrapper에 hook을 래핑할 수 있습니다.
테스트당 한 번 실행되기 때문에 이것이 QueryClient를 생성하기에 완벽한 장소라고 생각합니다.

컨텍스트 내에서 커스텀 훅이 호출되는지 여부 확인

const createWrapper = () => {
  // ✅ creates a new QueryClient for each test
  const queryClient = new QueryClient()
  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
}

 

컴포넌트 테스트

useQuery hook을 사용하는 컴포넌트를 테스트하려면 해당 컴포넌트를 QueryClientProvider에 래핑해야 합니다.
react-testing-library의 작은 래퍼가 좋은 선택인 것 같습니다.
React Query 내부 테스트를 살펴보세요 : internally for their tests

재시도 설정 끄기

React Query 라이브러리는 기본적으로 지수 백오프를 사용하여 3회 재시도하도록 설정되어 있습니다.
잘못된 쿼리를 테스트 시 오류 결과가 시간 초과로 잘못 나올 수 있음을 의미합니다.
재시도를 해제하는 가장 쉬운 방법은 다시 QueryClientProvider를 사용하는 것입니다.
위의 예를 확장해 보겠습니다.
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        // ✅ turns retries off
        retry: false,
      },
    },
  })

  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
}
이렇게 하면 컴포넌트 트리의 모든 쿼리에 대한 기본값이 "재시도 없음"으로 설정됩니다.
useQuery의 명시적 재시도 설정은 해당 디폴트 설정을 덮어씁니다.

setQueryDefaults

 

🚨 useQuery에서 테스트 옵션을 직접 설정하지 마세요

기본값을  사용하거나, queryClient를사용하고, 필요하면 queryClient.setQueryDefaults를 사용합니다.
 
🚨 예를 들어 useQuery에서 재시도를 설정하는 대신:
const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

function Example() {
  // 🚨 you cannot override this setting for tests!
  const queryInfo = useQuery('todos', fetchTodos, { retry: 5 })
}​

 

 

✅ 이렇게 하세요

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
    },
  },
})

// ✅ only todos will retry 5 times
queryClient.setQueryDefaults('todos', { retry: 5 })

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}​

 

모든 쿼리는 2번 재시도하고 todos만 5번 재시도하며,

테스트의 모든 쿼리에 대해 이 기능을 끌 수 있습니다🙌.


ReactQueryConfigProvider

때로는 컴포넌트 트리의 특정 하위 집합에만 설정을 적용하고 싶을 때가 있습니다.
const ReactQueryConfigProvider = ({ children, defaultOptions }) => {
  const client = useQueryClient()
  const [newClient] = React.useState(
    () =>
      new QueryClient({
        queryCache: client.getQueryCache(),
        muationCache: client.getMutationCache(),
        defaultOptions,
      })
  )

  return (
    <QueryClientProvider client={newClient}>{children}</QueryClientProvider>
  )
}​

항상 쿼리 응답 기다리기

React Query는 본질적으로 비동기이기 때문에 hook 실행 즉시 결과를 얻지는 못합니다.
일반적으로 훅 호출 다음은 로드 상태이고 이 때는 확인할 데이터가 없습니다.
react-hooks-testing-library의 비동기 유틸리티는 이 문제를 해결할 수 있는 많은 방법을 제공합니다.
가장 간단한 경우, 쿼리가 성공 상태로 전환될 때까지 기다리면 됩니다.
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  })
  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

test("my first test", async () => {
  const { result, waitFor } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })

  // ✅ wait until the query has transitioned to success state
  await waitFor(() => result.current.isSuccess)

  expect(result.current.data).toBeDefined()
}

@testing-library/react v13.1.0에는 renderHook도 있습니다. (리액트-훅-테스트-라이브러리 대신 사용 가능한 듯...)

그러나 자체적으로 waitFor 유틸리티를 반환하지 않으므로 @testing-library/react에서 가져올 수 있는 유틸리티를 대신 사용해야 합니다.

API는 부울 값 대신 promise를 반환하는 것을 기대하기에, 코드를 약간 수정합니다.

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

test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })

  // ✅ return a Promise via expect to waitFor
  await waitFor(() => expect(result.current.isSuccess).toBe(true))

  expect(result.current.data).toBeDefined()
}

에러 콘솔 끄기

기본적으로 React Query는 콘솔에 오류를 출력합니다.
모든 테스트가 🟢임에도 불구하고 콘솔에서 🔴를 볼 수 있기 때문에 테스트시 방해됩니다.
React Query를 사용하면 로거를 설정하여 기본 동작을 덮어쓸 수 있습니다.
 
const queryClient = new QueryClient({
  logger: {
    log: console.log,
    warn: console.warn,
    // ✅ no more errors on the console
    error: () => {},
  }
})


정리하며

msw와 react-testing-library를 이용해 구현한,

커스텀 훅 및 컴포넌트에 대한 실패 및 성공 네 가지 테스트 케이스를 살펴보세요.

 

 

반응형