Framework
Version

Testen

React Query funktioniert über Hooks - entweder die von uns angebotenen oder benutzerdefinierte, die sie umhüllen.

Mit React 17 oder früher können Unit-Tests für diese benutzerdefinierten Hooks mit der React Hooks Testing Library-Bibliothek geschrieben werden.

Installieren Sie dies, indem Sie Folgendes ausführen:

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

(Die react-test-renderer-Bibliothek wird als Peer-Abhängigkeit von @testing-library/react-hooks benötigt und muss der von Ihnen verwendeten React-Version entsprechen.)

Hinweis: Bei Verwendung von React 18 oder höher ist renderHook direkt über das Paket @testing-library/react verfügbar, und @testing-library/react-hooks wird nicht mehr benötigt.

Unser erster Test

Nach der Installation kann ein einfacher Test geschrieben werden. Gegeben sei der folgende benutzerdefinierte Hook:

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

Wir können dafür wie folgt einen Test schreiben:

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

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

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

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

expect(result.current.data).toEqual('Hello')
import { renderHook, waitFor } from '@testing-library/react'

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

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

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

expect(result.current.data).toEqual('Hello')

Beachten Sie, dass wir einen benutzerdefinierten Wrapper bereitstellen, der den QueryClient und QueryClientProvider erstellt. Dies hilft sicherzustellen, dass unser Test vollständig von anderen Tests isoliert ist.

Es ist möglich, diesen Wrapper nur einmal zu schreiben, aber dann müssen wir sicherstellen, dass der QueryClient vor jedem Test gelöscht wird und dass die Tests nicht parallel ausgeführt werden, da sonst ein Test die Ergebnisse anderer beeinflussen würde.

Wiederholungsversuche deaktivieren

Die Bibliothek verwendet standardmäßig drei Wiederholungsversuche mit exponentiellem Backoff, was bedeutet, dass Ihre Tests wahrscheinlich fehlschlagen, wenn Sie eine fehlerhafte Abfrage testen möchten. Der einfachste Weg, Wiederholungsversuche zu deaktivieren, ist über den QueryClientProvider. Erweitern wir das obige Beispiel:

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

Dadurch werden die Standardeinstellungen für alle Abfragen im Komponententree auf "keine Wiederholungsversuche" gesetzt. Es ist wichtig zu wissen, dass dies nur funktioniert, wenn Ihre tatsächliche useQuery keine expliziten Wiederholungsversuche eingestellt hat. Wenn eine Abfrage 5 Wiederholungsversuche haben soll, wird dies trotzdem Vorrang haben, da Standardwerte nur als Fallback genommen werden.

gcTime auf Infinity mit Jest setzen

Wenn Sie Jest verwenden, können Sie gcTime auf Infinity setzen, um die Fehlermeldung "Jest did not exit one second after the test run completed" zu vermeiden. Dies ist das Standardverhalten auf dem Server und muss nur dann eingestellt werden, wenn Sie explizit eine gcTime festlegen.

Netzwerkanfragen testen

Der Hauptzweck von React Query ist das Caching von Netzwerkanfragen. Daher ist es wichtig, dass wir testen können, ob unser Code überhaupt die richtigen Netzwerkanfragen stellt.

Es gibt viele Möglichkeiten, diese zu testen, aber für dieses Beispiel werden wir nock verwenden.

Gegeben sei der folgende benutzerdefinierte Hook:

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

Wir können dafür wie folgt einen Test schreiben:

tsx
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 } = renderHook(() => useFetchData(), { wrapper })

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

expect(result.current.data).toEqual({ answer: 42 })
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 } = renderHook(() => useFetchData(), { wrapper })

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

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

Hier verwenden wir waitFor und warten, bis der Abfragestatus angibt, dass die Anfrage erfolgreich war. So wissen wir, dass unser Hook abgeschlossen ist und die richtigen Daten haben sollte. Hinweis: Bei Verwendung von React 18 haben sich die Semantiken von waitFor wie oben erwähnt geändert.

Testen von "Mehr laden" / Unendliches Scrollen

Zuerst müssen wir unsere API-Antwort simulieren:

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

Dann muss unsere nock-Konfiguration Antworten basierend auf der Seite unterscheiden, und wir verwenden uri dafür. Der Wert von uri wird hier etwas wie "/?page=1 oder /?page=2 sein.

tsx
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)
  })
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)
  })

(Beachten Sie das .persist(), da wir diese Endpunkt mehrmals aufrufen werden)

Jetzt können wir unsere Tests sicher ausführen. Der Trick hierbei ist, auf die Datenassertion zu warten, bis sie erfolgreich ist:

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

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

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()
const { result } = renderHook(() => useInfiniteQueryCustomHook(), {
  wrapper,
})

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

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()

Hinweis: Bei Verwendung von React 18 haben sich die Semantiken von waitFor wie oben erwähnt geändert.

Weiterführende Lektüre

Für zusätzliche Tipps und eine alternative Einrichtung mit mock-service-worker schauen Sie sich Testing React Query aus den Community-Ressourcen an.