Testing HttpClient GET requests in Angular

August 20th 2021 Angular Unit Testing

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.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License