Testing failing HTTP requests in Angular
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.