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 method handleError
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 calls console.error
. Documentation doesn't have any example to my situation. Any help or suggestion is appreciated.
While writing the Unit test for services using HTTPClient, you may find it is very useful to use HttpClientTestingModule. This module helps in testing and mocking especially data services that make HTTP calls to the servers.
Prefer spies as they are usually the best way to mock services. These standard testing techniques are great for unit testing services in isolation. However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.
A response that represents an error or failure, either from a non-successful HTTP status, an error while executing the request, or some other failure which occurred during the parsing of the response.
For click event we can use triggerEventHandler method of Angular DebugElement class. We can also call native JavaScript click method of button. On click of button, we call a component method and it is possible that our component method has other dependencies to execute.
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'));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With