Testing failing HTTP requests in Angular

August 27th 2021 Angular Unit Testing

I really like Angular's support for unit testing HTTP requests, even if I find the documentation a bit spotty. I struggled a bit when I first tried to test error handling.

This is the general idea for the code I wanted to test:

@Injectable({
  providedIn: "root",
})
export class HttpService {
  private readonly url = "https://fake.url";

  constructor(private http: HttpClient) {}

  public get(id: string): Observable<string | undefined> {
    return this.http.get<string>(`${this.url}/${id}`).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 404) {
          return of(undefined);
        } else {
          return throwError(error.error);
        }
      })
    );
  }
}

The service that makes the HTTP calls includes error handling that can handle different HTTP response status codes differently. To test this, I need to return specific status codes with specific response bodies and validate the behavior of the method in those cases.

Looking at the TestRequest documentation, you might think that the error method is a good choice. But it isn't. It does allow you to set status codes, but there's no way to return a custom response body. Therefore, the HttpErrorResponse.error property will always be an ErrorEvent instance. This is because the method is intended to simulate network errors.

To simulate error responses from the server, you should use the flush method instead. It allows you to specify the status code (and message) in addition to the response body. The application code that checks the HttpErrorResponse.status property will receive the specified status code:

it("should return undefined when server returns 404", () => {
  let succeeded = false;
  let body: string | undefined;

  service.get("42").subscribe((response) => {
    succeeded = true;
    body = response;
  });

  const testRequest = httpMock.expectOne("https://fake.url/42");
  testRequest.flush("", { status: 404, statusText: "Not Found" });

  expect(succeeded).toBeTrue();
  expect(body).toBeUndefined();
});

Any response body you include in the flush call is available to the application code in the HttpErrorResponse.error property:

it("should throw error with response body when server returns error other than 404", () => {
  let body: ErrorDetails | undefined;

  service.get("42").subscribe(
    () => {},
    (error: ErrorDetails) => {
      body = error;
    }
  );

  const expected: ErrorDetails = {
    code: "validationFailed",
    message: "Invalid input",
  };

  const testRequest = httpMock.expectOne("https://fake.url/42");
  testRequest.flush(expected, { status: 400, statusText: "Bad Request" });

  expect(body).toEqual(expected);
});

You can check the tests and sample application code in my GitHub repository.

The HttpClientTestingModule provides everything you need to test HTTP client code in your Angular applications. If you want to test handling different error responses from the server, don't get distracted by the TestRequest.error method. Just use the TestRequest.flush method as you do for successful server responses.

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