0% found this document useful (0 votes)
34 views8 pages

React Query

The document discusses various strategies for testing components that use React Query, including mocking network requests, configuring the QueryClientProvider, setting query defaults, and waiting for queries to complete before asserting results.

Uploaded by

ridiy42720
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
34 views8 pages

React Query

The document discusses various strategies for testing components that use React Query, including mocking network requests, configuring the QueryClientProvider, setting query defaults, and waiting for queries to complete before asserting results.

Uploaded by

ridiy42720
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 8

TkDodo's blog Search ⌘ K

Blog Tags Sponsors Rss Twitter Github

Testing React Query


04.04.2021 — ReactJs, React Query, JavaScript, TypeScript — 4 min read

Photo by Girl with red hat


Last Update: 21.10.2023
#1: Practical React Query
#2: React Query Data Transformations
#3: React Query Render Optimizations
#4: Status Checks in React Query
#5: Testing React Query
#6: React Query and TypeScript
#7: Using WebSockets with React Query
#8: Effective React Query Keys
#8a: Leveraging the Query Function Context
#9: Placeholder and Initial Data in React Query
#10: React Query as a State Manager
#11: React Query Error Handling
#12: Mastering Mutations in React Query
#13: Offline React Query
#14: React Query and Forms
#15: React Query FAQs
#16: React Query meets React Router
#17: Seeding the Query Cache
#18: Inside React Query
#19: Type-safe React Query
#20: You Might Not Need React Query
#21: Thinking in React Query
#22: React Query and React Context
#23: Why You Want React Query
#24: The Query Options API

한국어 Português Español 日本語 简体中文 Add translation

Questions around the testing topic come up quite often together with React Query, so I'll try to answer
some of them here. I think one reason for that is that testing "smart" components (also called container
components) is not the easiest thing to do. With the rise of hooks, this split has been largely deprecated. It
is now encouraged to consume hooks directly where you need them rather than doing a mostly arbitrary
split and drill props down.
I think this is generally a very good improvement for colocation and code readability, but we now have
more components that consume dependencies outside of "just props".
They might useContext . They might useSelector . Or they might useQuery .
Those components are technically no longer pure, because calling them in different environments leads to
different results. When testing them, you need to carefully setup those surrounding environments to get
things working.
Mocking network requests
Since React Query is an async server state management library, your components will likely make requests
to a backend. When testing, this backend is not available to actually deliver data, and even if, you likely
don't want to make your tests dependent on that.
There are tons of articles out there on how to mock data with jest. You can mock your api client if you have
one. You can mock fetch or axios directly. I can only second what Kent C. Dodds has written in his article
Stop mocking fetch:
Use mock service worker by @ApiMocking
It can be your single source of truth when it comes to mocking your apis:
works in node for testing
supports REST and GraphQL
has a storybook addon so you can write stories for your components that useQuery
works in the browser for development purposes, and you'll still see the requests going out in the
browser devtools
works with cypress, similar to fixtures
With our network layer being taken care of, we can start talking about React Query specific things to keep
an eye on:
QueryClientProvider
Whenever you use React Query, you need a QueryClientProvider and give it a queryClient - a vessel which
holds the QueryCache . The cache will in turn hold the data of your queries.
I prefer to give each test its own QueryClientProvider and create a new QueryClient for each test. That
way, tests are completely isolated from each other. A different approach might be to clear the cache after
each test, but I like to keep shared state between tests as minimal as possible. Otherwise, you might get
unexpected and flaky results if you run your tests in parallel.
For custom hooks
If you are testing custom hooks, I'm quite certain you're using react-hooks-testing-library. It's the easiest
thing there is to test hooks. With that library, we can wrap our hook in a wrapper, which is a React
component to wrap the test component in when rendering. I think this is the perfect place to create the
QueryClient, because it will be executed once per test:
wrapper
TSX Copy
1 const createWrapper = () => {
2 // ✅ creates a new QueryClient for each test
3 const queryClient = new QueryClient()
4 return ({ children }) => (
5 <QueryClientProvider client={queryClient}>
6 {children}
7 </QueryClientProvider>
8 )
9 }
10
11 test('my first test', async () => {
12 const { result } = renderHook(() => useCustomHook(), {
13 wrapper: createWrapper(),
14 })
15 })

For components
If you want to test a Component that uses a useQuery hook, you also need to wrap that Component in
QueryClientProvider. A small wrapper around render from react-testing-library seems like a good choice.
Have a look at how React Query does it internally for their tests.
Turn off retries
It's one of the most common "gotchas" with React Query and testing: The library defaults to three retries
with exponential backoff, which means that your tests are likely to timeout if you want to test an erroneous
query. The easiest way to turn retries off is, again, via the QueryClientProvider . Let's extend the above
example:
no-retries
TSX Copy
1 const createWrapper = () => {
2 const queryClient = new QueryClient({
3 defaultOptions: {
4 queries: {
5 // ✅turns retries off
6 retry: false,
7 },
8 },
9 })
10
11 return ({ children }) => (
12 <QueryClientProvider client={queryClient}>
13 {children}
14 </QueryClientProvider>
15 )
16 }
17
18 test("my first test", async () => {
19 const { result } = renderHook(() => useCustomHook(), {
20 wrapper: createWrapper()
21 })
22 }

This will set the defaults for all queries in the component tree to "no retries". It is important to know that
this will only work if your actual useQuery has no explicit retries set. If you have a query that wants 5
retries, this will still take precedence, because defaults are only taken as a fallback.
setQueryDefaults
The best advice I can give you for this problem is: Don't set these options on useQuery directly. Try to
use and override the defaults as much as possible, and if you really need to change something for specific
queries, use queryClient.setQueryDefaults.
So for example, instead of setting retry on useQuery :
not-on-useQuery
TSX Copy
1 const queryClient = new QueryClient()
2
3 function App() {
4 return (
5 <QueryClientProvider client={queryClient}>
6 <Example />
7 </QueryClientProvider>
8 )
9 }
10
11 function Example() {
12 // 🚨
you cannot override this setting for tests!
13 const queryInfo = useQuery({
14 queryKey: ['todos'],
15 queryFn: fetchTodos,
16 retry: 5,
17 })
18 }

Set it like this:


setQueryDefaults
TSX Copy
1 const queryClient = new QueryClient({
2 defaultOptions: {
3 queries: {
4 retry: 2,
5 },
6 },
7 })
8
9 // ✅only todos will retry 5 times
10 queryClient.setQueryDefaults(['todos'], { retry: 5 })
11
12 function App() {
13 return (
14 <QueryClientProvider client={queryClient}>
15 <Example />
16 </QueryClientProvider>
17 )
18 }

Here, all queries will retry two times, only todos will retry five times, and I still have the option to turn it off
for all queries in my tests 🙌 .
ReactQueryConfigProvider
Of course, this only works for known query keys. Sometimes, you really want to set some configs on a
subset of your component tree. In v2, React Query had a ReactQueryConfigProvider for that exact use-
case. You can achieve the same thing in v3 with a couple of lines of codes:
ReactQueryConfigProvider
JSX Copy
1 const ReactQueryConfigProvider = ({ children, defaultOptions }) => {
2 const client = useQueryClient()
3 const [newClient] = React.useState(
4 () =>
5 new QueryClient({
6 queryCache: client.getQueryCache(),
7 muationCache: client.getMutationCache(),
8 defaultOptions,
9 })
10 )
11
12 return (
13 <QueryClientProvider client={newClient}>
14 {children}
15 </QueryClientProvider>
16 )
17 }

You can see this in action in this codesandbox example.


Always await the query
Since React Query is async by nature, when running the hook, you won't immediately get a result. It usually
will be in loading state and without data to check. The async utilities from react-hooks-testing-library offer
a lot of ways to solve this problem. For the simplest case, we can just wait until the query has transitioned
to success state:
waitFor
TSX Copy
1 const createWrapper = () => {
2 const queryClient = new QueryClient({
3 defaultOptions: {
4 queries: {
5 retry: false,
6 },
7 },
8 })
9 return ({ children }) => (
10 <QueryClientProvider client={queryClient}>
11 {children}
12 </QueryClientProvider>
13 )
14 }
15
16 test("my first test", async () => {
17 const { result, waitFor } = renderHook(() => useCustomHook(), {
18 wrapper: createWrapper()
19 })
20
21 // ✅
wait until the query has transitioned to success state
22 await waitFor(() => result.current.isSuccess)
23
24 expect(result.current.data).toBeDefined()
25 }

Update
@testing-library/react v13.1.0 also has a new renderHook that you can use. However, it doesn't return
its own waitFor util, so you'll have to use the one you can import from @testing-library/react
instead. The API is a bit different, as it doesn't allow to return a boolean , but expects a Promise
instead. That means we must adapt our code slightly:
new-render-hook
TSX Copy
1 import { waitFor, renderHook } from '@testing-library/react'
2
3 test("my first test", async () => {
4 const { result } = renderHook(() => useCustomHook(), {
5 wrapper: createWrapper()
6 })
7
8 // ✅
return a Promise via expect to waitFor
9 await waitFor(() => expect(result.current.isSuccess).toBe(true))
10
11 expect(result.current.data).toBeDefined()
12 }

Putting it all together


I've set up a quick repository where all of this comes nicely together: mock-service-worker, react-testing-
library and the mentioned wrapper. It contains four tests - basic failure and success tests for custom hooks
and components. Have a look here: https://github.com/TkDodo/testing-react-query
That's it for today. Feel free to reach out to me on twitter if you have any questions, or just leave a
comment below. ⬇️
Like the monospace font in the code blocks?
Check out monolisa.dev

© 2024 by TkDodo's blog. All rights reserved.


Theme by LekoArts

You might also like