forked from TanStack/query
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(docs): add the angular testing documentation #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
arvi18
wants to merge
1
commit into
main
Choose a base branch
from
clone-feat/docsAngularTesting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
--- | ||
id: testing | ||
title: Testing | ||
--- | ||
|
||
As there is currently no simple way to await a signal to reach a specific value we will use polling to wait in our test (instead of transforming our signals in observable and use RxJS features to filter the values). If you want to do like us for the polling you can use the angular testing library. | ||
|
||
Install this by running: | ||
|
||
```sh | ||
ng add @testing-library/angular | ||
``` | ||
|
||
Otherwise we recommend to use the toObservable feature from Angular. | ||
|
||
## What to test | ||
|
||
Because the recommendation is to use services that provide the Query options through function this is what we are going to do. | ||
|
||
## A simple test | ||
|
||
```ts | ||
//tasks.service.ts | ||
import { HttpClient } from '@angular/common/http' | ||
import { Injectable, inject } from '@angular/core' | ||
import { | ||
QueryClient, | ||
mutationOptions, | ||
queryOptions, | ||
} from '@tanstack/angular-query-experimental' | ||
|
||
import { lastValueFrom } from 'rxjs' | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class TasksService { | ||
#queryClient = inject(QueryClient) // Manages query state and caching | ||
#http = inject(HttpClient) // Handles HTTP requests | ||
|
||
/** | ||
* Fetches all tasks from the API. | ||
* Returns an observable containing an array of task strings. | ||
*/ | ||
allTasks = () => | ||
queryOptions({ | ||
queryKey: ['tasks'], | ||
queryFn: () => { | ||
return lastValueFrom(this.#http.get<Array<string>>('/api/tasks')); | ||
} | ||
}) | ||
} | ||
``` | ||
|
||
```ts | ||
// tasks.service.spec.ts | ||
import { TestBed } from "@angular/core/testing"; | ||
import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http"; | ||
import { QueryClient, injectQuery, provideTanStackQuery } from "@tanstack/angular-query-experimental"; | ||
import { Injector, inject, runInInjectionContext } from "@angular/core"; | ||
import { waitFor } from '@testing-library/angular'; | ||
import { mockInterceptor } from "../interceptor/mock-api.interceptor"; | ||
import { TasksService } from "./tasks.service"; | ||
import type { CreateQueryResult} from "@tanstack/angular-query-experimental"; | ||
|
||
describe('Test suite: TaskService', () => { | ||
let service!: TasksService; | ||
let injector!: Injector; | ||
|
||
// https://angular.dev/guide/http/testing | ||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
providers: [ | ||
provideHttpClient(withFetch(), withInterceptors([mockInterceptor])), | ||
TasksService, | ||
// It is recommended to cancel the retries in the tests | ||
provideTanStackQuery(new QueryClient({ | ||
defaultOptions: { | ||
queries: { | ||
retry: false | ||
} | ||
} | ||
})) | ||
] | ||
}); | ||
service = TestBed.inject(TasksService); | ||
injector = TestBed.inject(Injector); | ||
}); | ||
|
||
it('should get all the Tasks', () => { | ||
let allTasks: any; | ||
runInInjectionContext(injector, () => { | ||
allTasks = injectQuery(() => service.allTasks()); | ||
}); | ||
expect(allTasks.status()).toEqual('pending'); | ||
expect(allTasks.isFetching()).toEqual(true); | ||
expect(allTasks.data()).toEqual(undefined); | ||
// We await the first result from the query | ||
await waitFor(() => expect(allTasks.isFetching()).toBe(false), {timeout: 10000}); | ||
expect(allTasks.status()).toEqual('success'); | ||
expect(allTasks.data()).toEqual([]); // Considering that the inteceptor is returning [] at the first query request. | ||
// To have a more complete example have a look at "unit testing / jest" | ||
}); | ||
}); | ||
``` | ||
|
||
```ts | ||
// mock-api.interceptor.ts | ||
/** | ||
* MockApiInterceptor is used to simulate API responses for `/api/tasks` endpoints. | ||
* It handles the following operations: | ||
* - GET: Fetches all tasks from sessionStorage. | ||
* - POST: Adds a new task to sessionStorage. | ||
* Simulated responses include a delay to mimic network latency. | ||
*/ | ||
import { HttpResponse } from '@angular/common/http' | ||
import { delay, of, throwError } from 'rxjs' | ||
import type { | ||
HttpEvent, | ||
HttpHandlerFn, | ||
HttpInterceptorFn, | ||
HttpRequest, | ||
} from '@angular/common/http' | ||
import type { Observable } from 'rxjs' | ||
|
||
export const mockInterceptor: HttpInterceptorFn = ( | ||
req: HttpRequest<unknown>, | ||
next: HttpHandlerFn, | ||
): Observable<HttpEvent<any>> => { | ||
const respondWith = (status: number, body: any) => | ||
of(new HttpResponse({ status, body })).pipe(delay(1000)) | ||
if (req.url === '/api/tasks') { | ||
switch (req.method) { | ||
case 'GET': | ||
return respondWith( | ||
200, | ||
JSON.parse( | ||
sessionStorage.getItem('unit-testing-tasks') || '[]', | ||
), | ||
) | ||
case 'POST': | ||
const tasks = JSON.parse( | ||
sessionStorage.getItem('unit-testing-tasks') || '[]', | ||
) | ||
tasks.push(req.body) | ||
sessionStorage.setItem( | ||
'unit-testing-tasks', | ||
JSON.stringify(tasks), | ||
) | ||
return respondWith(201, { | ||
status: 'success', | ||
task: req.body, | ||
}) | ||
} | ||
} | ||
if (req.url === '/api/tasks-wrong-url') { | ||
return throwError(() => new Error('error')).pipe(delay(1000)); | ||
} | ||
|
||
return next(req) | ||
} | ||
``` | ||
|
||
## Turn off retries | ||
|
||
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 via the provideTanStackQuery during the TestBed setup as shown in the above example. | ||
|
||
## Testing Network Calls | ||
|
||
Instead of targetting a server for the data you should mock the requests. There are multiple way of handling the mocking, we recommend to use the Interceptor from Angular, see [here](https://angular.dev/guide/http/interceptors) for more details. | ||
You can see the the Interceptor setup in the "Unit testing / Jest" examples. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
examples/angular/unit-testing/.devcontainer/devcontainer.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "Node.js", | ||
"image": "mcr.microsoft.com/devcontainers/javascript-node:22" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// @ts-check | ||
|
||
/** @type {import('eslint').Linter.Config} */ | ||
const config = {} | ||
|
||
module.exports = config |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# TanStack Query Angular unit-testing example | ||
|
||
To run this example: | ||
|
||
- `npm install` or `yarn` or `pnpm i` or `bun i` | ||
- `npm run start` or `yarn start` or `pnpm start` or `bun start` | ||
- `npm run test` to run the tests |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Polish grammar, punctuation, and style for clarity and professionalism.
The technical content is excellent, but the following refinements are recommended:
Here is a suggested diff for the most important corrections:
Also applies to: 13-14, 18-18, 166-166, 170-171
🧰 Tools
🪛 LanguageTool
[uncategorized] ~6-~6: A comma might be missing here.
Context: ...y to await a signal to reach a specific value we will use polling to wait in our test...
(AI_EN_LECTOR_MISSING_PUNCTUATION_COMMA)
[uncategorized] ~6-~6: The preposition “into” seems more likely in this position than the preposition “in”.
Context: ...st (instead of transforming our signals in observable and use RxJS features to fil...
(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_INTO)
[uncategorized] ~6-~6: This verb may not be in the correct form. Consider using a different form for this context.
Context: ...nsforming our signals in observable and use RxJS features to filter the values). If...
(AI_EN_LECTOR_REPLACEMENT_VERB_FORM)
[uncategorized] ~6-~6: A comma might be missing here.
Context: ...ues). If you want to do like us for the polling you can use the angular testing library...
(AI_EN_LECTOR_MISSING_PUNCTUATION_COMMA)