How to mock Angular 4.3 httpClient an error response in testing
The expectOne
method in HttpTestingController
class returns a TestRequest
object. This TestRequest class has a flush
method which can be used to deliver
both successful and unsuccessful responses.
We can resolve the request by returning a body along with some additional response headers (if any). Relevant info can be found here.
Now, coming back to the point how you can do this. You can customize the below code snippet as per your use case.
http = TestBed.get(HttpTestingController);
let response: any;
let errResponse: any;
const mockErrorResponse = { status: 400, statusText: 'Bad Request' };
const data = 'Invalid request parameters';
apiService.get(somePath).subscribe(res => response = res, err => errResponse = err);
http.expectOne('url/being/monitored').flush(data, mockErrorResponse);
expect(errResponse).toBe(data);
NOTE: At the time of writing this comment, statusText
is required in mockErrorResponse
. Related info can be found here.
P.S.: The error
method of TestRequest
class can be used to simulate network error in our test case, as it expects an instance of Error. The following code snippet shows that.
http.expectOne(someUrl).error(new ErrorEvent('network error'));
Related videos on Youtube
Sai Ram Gupta
I am UI developer who is very much passionate about learning exciting things in the UI
Updated on July 09, 2022Comments
-
Sai Ram Gupta almost 2 years
I have a below
interceptor auth-interceptor.service.ts
import {Injectable, Injector} from '@angular/core'; import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {Observable} from 'rxjs/Observable'; import {Cookie} from './cookie.service'; import {Router} from '@angular/router'; import {UserService} from './user.service'; import {ToasterService} from '../toaster/toaster.service'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private injector: Injector) {} private handleError(err: HttpErrorResponse): Observable<any> { let errorMsg; if (err.error instanceof Error) { // A client-side or network error occurred. Handle it accordingly. errorMsg = `An error occurred: ${err.error.message}`; } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, errorMsg = `Backend returned code ${err.status}, body was: ${err.error}`; } if (err.status === 401 || err.status === 403) { this.injector.get(UserService).purgeAuth(); this.injector.get(ToasterService).showError(`Unauthorized`, errorMsg); this.injector.get(Router).navigateByUrl(`/login`); } console.error(errorMsg); return Observable.throw(errorMsg); } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // Clone the request to add the new header. const authReq = req.clone({headers: req.headers.set(Cookie.tokenKey, Cookie.getToken())}); // Pass on the cloned request instead of the original request. return next.handle(authReq).catch(err => this.handleError(err)); } }
Now I am trying to mock the
http.get
to throw the error, so that methodhandleError
consoles the error message.Below is my approach to the test case
auth-interceptor.service.specs.ts
import {async, inject, TestBed} from '@angular/core/testing'; import {AuthInterceptor} from './auth-interceptor.service'; import {ApiService} from './api.service'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {environment} from '../../../environments/environment'; describe(`AuthInterceptor`, () => { const somePath = `/somePath`; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [AuthInterceptor, ApiService] }); }); it(`should be created`, inject([AuthInterceptor], (service: AuthInterceptor) => { expect(service).toBeTruthy(); })); it(`should log an error to the console on error on get()`, async(inject([ApiService, HttpTestingController], (apiService: ApiService, httpMock: HttpTestingController) => { spyOn(console, 'error'); apiService.get(somePath).subscribe((res) => { console.log(`in success:`, res); }, (error) => { console.log(`in error:`, error); }); const req = httpMock.expectOne(`${environment.apiUri}${somePath}`); req.flush({ type: 'ERROR', status: 404, body: JSON.stringify({color: `blue`}) }); expect(console.error).toHaveBeenCalled(); })) ); });
When flushing the response, I am not sure how to flush a error response, so that the method
handleError
will be called in my interceptor and that eventually callsconsole.error
. Documentation doesn't have any example to my situation. Any help or suggestion is appreciated.-
Anton Belonovich almost 7 yearsYour
req
object is an instance ofTestRequest
class. It also haserror()
method. Did you try something likereq.error(new ErrorEvent('fail'), {status: 404});
?
-
-
Michael McKenna about 5 yearsIf checking an error, should you not instead do
http.expectOne(someUrl).error(null, mockErrorResponse)
? Creating anErrorEvent
doesn't allow you to include a status code, so to me it seems less desirable than my proposed solution. -
Jimbo almost 4 yearsI am struggling with this. If I have one jasmine unit test doing this it works. If I have more than one, it is actually throwing the error I am trying to mock and killing the test cycle.
-
Alin Ciocan almost 4 years@Jimbo I had the same issue. My problem was that I was not adding the error callback.
apiService.get(somePath).subscribe(res => response = res, err => errResponse = err);
The second callback is really important and it could even be an empty function like(err) => {}
. -
MikhailRatner about 2 yearsThank you, this worked! I like to add, that inside expectOne, you do not need an url, but you can just write
() => true
. Also inside of the subscribe you do not need the first response part, just theerrResponse
part is sufficient for this test.