Testing HttpClient GET requests in Angular
Angular does a lot to make testing your code easier. This includes a dedicated module for testing HttpClient
code. However, the way HttpTestingController
matches requests makes it unnecessarily difficult to test GET
requests with query parameters.
The behavior can be easily reproduced with any service that makes GET
requests with HttpClient
:
@Injectable({
providedIn: "root",
})
export class HttpService {
private readonly url = "https://fake.url";
constructor(private http: HttpClient) {}
public get(id: string): Observable<string> {
const params = new HttpParams().append("id", id);
return this.http.get<string>(this.url, { params });
}
}
I expected to be able to match the request in the test based on the URL:
it("should match request by URL", () => {
service.get("42").subscribe((response) => {
expect(response).toBe("response");
});
const testRequest = httpMock.expectOne("https://fake.url");
expect(testRequest.request.method).toBe("GET");
expect(testRequest.request.params.get("id")).toBe("42");
testRequest.flush("response");
});
But the test fails with the following error:
Error: Expected one matching request for criteria "Match URL:
https://fake.url
", found none. Requests received are:GET https://fake.url?id=42
.
Apparently, the query parameters must be included in the given URL. This means that the following test succeeds:
it("should match request by URL with params", () => {
service.get("42").subscribe((response) => {
expect(response).toBe("response");
});
const testRequest = httpMock.expectOne("https://fake.url?id=42");
expect(testRequest.request.method).toBe("GET");
expect(testRequest.request.params.get("id")).toBe("42");
testRequest.flush("response");
});
If there is more than one query parameter, this approach may make the test unreliable because it requires a specific order of the parameters.
Fortunately, there is a way to match requests by URL only when using a different overload of the expectOne
method:
it("should match request by matcher function", () => {
service.get("42").subscribe((response) => {
expect(response).toBe("response");
});
const testRequest = httpMock.expectOne(
(request) => request.url === "https://fake.url"
);
expect(testRequest.request.method).toBe("GET");
expect(testRequest.request.params.get("id")).toBe("42");
testRequest.flush("response");
});
I created a sample project with all three tests and pushed it to my GitHub repository.
When passing the URL of a GET
request to the expectOne
method, it must contain all the query parameters in the correct order to find a match. This makes the test less reliable. A better approach is to use a different overload with a matcher function that can check the value of the URL without query parameters.