I've recently upgraded my Angular5 project to Angular6 and one of the things I've had to change is around the usage of HttpClient, with the introduction of tap and pipe.
My code is now as follows:
public getCountryData(id: string): Country[] {
const url = `${this.config.apiUrl}/${id}`;
return this.http.get<CountryData>(url, this.noCacheOptions).pipe(
tap(res => {
res = this.convertToCountryArray(res);
return res;
}),
catchError(error => this.handleError(error))
);
}
I am writing tests to cover my API calls, I need to test a successful get and one with an error, so I've written the following test (preface - I'm a novice at writing these Unit tests, so no doubt it can be done better and any advice is appreciated!):
it('should call getCountryData, inject([HttpTestingController, CountryDataService, ConfigService],
(httpMock: HttpTestingController, dataService: CountryDataService, configService: ConfigService) => {
expect(CountryDataService).toBeTruthy();
const id = 'cfc78ed9-4e771215efdd64b1';
dataService.getCountryData(id).subscribe((event: any) => {
switch (event.type) {
case HttpEventType.Response:
expect(event.body).toEqual(mockCountryList);
}
}, (err) => {
expect(err).toBeTruthy();
});
const mockReq = httpMock.expectOne(`${configService.apiUrl}/${id}`);
expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.responseType).toEqual('json');
try {
mockReq.flush(new Error(), { status: 404, statusText: 'Bad Request' });
} catch (error) {
expect(error).toBeTruthy();
}
httpMock.verify();
})
);
When I look at the code coverage, I can see that not all branches are covered by my test. BUT I'm not sure what to change to get this covered properly. Any pointers or advice on what I need to change would be awesome!
Thanks in advance!
So I quickly spotted how to work around this. Just posting in case anyone else finds it useful. I split my positive and negative tests of the api into two unit tests, as follows:
it('should call getCountryData with success', () => {
const id = 'cfc78ed9-4e771215efdd64b1';
dataService.getCountryData(id).subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Response:
expect(event.body).toEqual(mockCountryData);
}
});
const mockReq = httpMock.expectOne(`${configService.apiUrl}/${id}`);
expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush(mockCountryData);
httpMock.verify();
});
it('should call getCountryData with failure', () => {
const id = 'cfc78ed9-4e771215efdd64b1';
dataService.getCountryData(id).subscribe((event: HttpEvent<any>) => { },
(err) => {
expect(err).toBeTruthy();
});
const mockReq = httpMock.expectOne(`${configService.apiUrl}/${id}`);
expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush({ errorType: 2,
errorDetails: [{ errorKey: 'errorKey', errorCode: '1001', errorMessage: 'errorMessage' }],
errorIdentifier: 'cfc78ed9-4e771215efdd64b1' },
{ status: 404, statusText: 'Bad Request' });
httpMock.verify();
});
Main difference is that one flushes the success object and one flushes the error.