Skip to content

Commit 06e5550

Browse files
authored
docs(angular-query): add testing guide (#9669)
1 parent 5d7c90e commit 06e5550

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,10 @@
707707
"label": "Default Query Fn",
708708
"to": "framework/angular/guides/default-query-function"
709709
},
710+
{
711+
"label": "Testing",
712+
"to": "framework/angular/guides/testing"
713+
},
710714
{
711715
"label": "Does this replace state managers?",
712716
"to": "framework/angular/guides/does-this-replace-client-state"
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
id: testing
3+
title: Testing
4+
---
5+
6+
Most Angular tests using TanStack Query will involve services or components that call `injectQuery`/`injectMutation`.
7+
8+
TanStack Query's `inject*` functions integrate with [`PendingTasks`](https://angular.dev/api/core/PendingTasks) which ensures the framework is aware of in-progress queries and mutations.
9+
10+
This means tests and SSR can wait until mutations and queries resolve. In unit tests you can use `ApplicationRef.whenStable()` or `fixture.whenStable()` to await query completion. This works for both Zone.js and Zoneless setups.
11+
12+
> This integration requires Angular 19 or later. Earlier versions of Angular do not support `PendingTasks`.
13+
14+
## TestBed setup
15+
16+
Create a fresh `QueryClient` for every spec and provide it with `provideTanStackQuery` or `provideQueryClient`. This keeps caches isolated and lets you change default options per test:
17+
18+
```ts
19+
const queryClient = new QueryClient({
20+
defaultOptions: {
21+
queries: {
22+
retry: false, // ✅ faster failure tests
23+
},
24+
},
25+
})
26+
27+
TestBed.configureTestingModule({
28+
providers: [provideTanStackQuery(queryClient)],
29+
})
30+
```
31+
32+
> If your applications actual TanStack Query config is used in unit tests, make sure `withDevtools` is not accidentally included in test providers. This can cause slow tests. It is best to keep test and production configs separate.
33+
34+
If you share helpers, remember to call `queryClient.clear()` (or build a new instance) in `afterEach` so data from one test never bleeds into another.
35+
36+
## First query test
37+
38+
Query tests typically run inside `TestBed.runInInjectionContext`, then wait for stability:
39+
40+
```ts
41+
const appRef = TestBed.inject(ApplicationRef)
42+
const query = TestBed.runInInjectionContext(() =>
43+
injectQuery(() => ({
44+
queryKey: ['greeting'],
45+
queryFn: () => 'Hello',
46+
})),
47+
)
48+
49+
TestBed.tick() // Trigger effect
50+
51+
// Application is stable when queries are idle
52+
await appRef.whenStable()
53+
54+
expect(query.status()).toBe('success')
55+
expect(query.data()).toBe('Hello')
56+
```
57+
58+
PendingTasks will have `whenStable()` resolve after the query settles. When using fake timers (Vitest), advance the clock and a microtask before awaiting stability:
59+
60+
```ts
61+
await vi.advanceTimersByTimeAsync(0)
62+
await Promise.resolve()
63+
await appRef.whenStable()
64+
```
65+
66+
## Testing components
67+
68+
For components, bootstrap them through `TestBed.createComponent`, then await `fixture.whenStable()`:
69+
70+
```ts
71+
const fixture = TestBed.createComponent(ExampleComponent)
72+
73+
await fixture.whenStable()
74+
expect(fixture.componentInstance.query.data()).toEqual({ value: 42 })
75+
```
76+
77+
## Handling retries
78+
79+
Retries slow failing tests because the default backoff runs three times. Set `retry: false` (or a specific number) through `defaultOptions` or per query to keep tests fast. If a query intentionally retries, assert on the final state rather than intermediate counts.
80+
81+
## HttpClient & network stubs
82+
83+
Angular's `HttpClientTestingModule` plays nicely with PendingTasks. Register it alongside the Query provider and flush responses through `HttpTestingController`:
84+
85+
```ts
86+
TestBed.configureTestingModule({
87+
imports: [HttpClientTestingModule],
88+
providers: [provideTanStackQuery(queryClient)],
89+
})
90+
91+
const httpCtrl = TestBed.inject(HttpTestingController)
92+
const query = TestBed.runInInjectionContext(() =>
93+
injectQuery(() => ({
94+
queryKey: ['todos'],
95+
queryFn: () => lastValueFrom(TestBed.inject(HttpClient).get('/api/todos')),
96+
})),
97+
)
98+
99+
const fixturePromise = TestBed.inject(ApplicationRef).whenStable()
100+
httpCtrl.expectOne('/api/todos').flush([{ id: 1 }])
101+
await fixturePromise
102+
103+
expect(query.data()).toEqual([{ id: 1 }])
104+
httpCtrl.verify()
105+
```
106+
107+
## Infinite queries & pagination
108+
109+
Use the same pattern for infinite queries: call `fetchNextPage()`, advance timers if you are faking time, then await stability and assert on `data().pages`.
110+
111+
```ts
112+
const infinite = TestBed.runInInjectionContext(() =>
113+
injectInfiniteQuery(() => ({
114+
queryKey: ['pages'],
115+
queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
116+
getNextPageParam: (last, all) => all.length + 1,
117+
})),
118+
)
119+
120+
await appRef.whenStable()
121+
expect(infinite.data().pages).toHaveLength(1)
122+
123+
await infinite.fetchNextPage()
124+
await vi.advanceTimersByTimeAsync(0)
125+
await appRef.whenStable()
126+
127+
expect(infinite.data().pages).toHaveLength(2)
128+
```
129+
130+
## Mutations and optimistic updates
131+
132+
```ts
133+
const mutation = TestBed.runInInjectionContext(() =>
134+
injectMutation(() => ({
135+
mutationFn: async (input: string) => input.toUpperCase(),
136+
})),
137+
)
138+
139+
mutation.mutate('test')
140+
141+
// Trigger effect
142+
TestBed.tick()
143+
144+
await appRef.whenStable()
145+
146+
expect(mutation.isSuccess()).toBe(true)
147+
expect(mutation.data()).toBe('TEST')
148+
```
149+
150+
## Quick checklist
151+
152+
- Fresh `QueryClient` per test (and clear it afterwards)
153+
- Disable or control retries to avoid timeouts
154+
- Advance timers + microtasks before `whenStable()` when using fake timers
155+
- Use `HttpClientTestingModule` or your preferred mock to assert network calls
156+
- Await `whenStable()` after every `refetch`, `fetchNextPage`, or mutation
157+
- Prefer `TestBed.runInInjectionContext` for service tests and `fixture.whenStable()` for component tests

0 commit comments

Comments
 (0)