ramon22
ramon22

Reputation: 3628

Testing Angular 4 HttpTestingController with catch and _throw error logic

I have Service using HttpClient using a POST request, if the server responds with and error I need to catch it format the message and re-throw it. I have tried to test this scenario, but failed to mock the test. The HttpTestingController is not sending my custom error message back nor is it catching it on the service and re-throwing it, Whats the proper way to do this

the service code :

 login(credentials: LoginPayload): Observable<LoginSuccessPayload> {

      return this.http.post<LoginSuccessPayload>('/api/auth/signin', credentials)
             .map(res => {authUser: res.user})
             .catch((error: HttpErrorResponse) => {
                if (error.message) {
                    return _throw(error);
                }
                return _throw({message: 'Notification.LoginError'});
             });

 }

now the test

beforeEach(() => {
    TestBed.configureTestingModule({
        imports: [HttpClientTestingModule],
        providers: [AuthService]
    });

    authService = TestBed.get(AuthService);
    httpMock = TestBed.get(HttpTestingController);
});

it('should format the error message', (done) => {
    const credentials = {userName: 'bob', password: '123'} as LoginPayload;
    const mockErrorResponse = {message: 'failed to login'} ;

    authService.login(credentials).subscribe(() => {}, err => {
        expect(err.message).toEqual(mockErrorResponse.message);
        done();
    });

    const req = httpMock.expectOne('/api/auth/signin');
    req.error(new ErrorEvent(mockErrorResponse.message));

    httpMock.verify();
});

Upvotes: 1

Views: 5490

Answers (3)

Rahul Wasnik
Rahul Wasnik

Reputation: 152

I tried the angular documentation approach link and it worked for me using the req.error. Hope this helps..

it('should handle an error', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {

    http.get<any>('http://localhost:4200/api/data').subscribe(
        response => fail('should fail with a 403 error'),
        error => {
            expect(error.status).toBe(403);
        }
    );

    // Mocking rejects with a 403 error
    let mockRequest = httpTestingController.expectOne(req =>  
                        req.url === 'http://localhost:4200/api/data');
    let error = new ErrorEvent('ERROR');
    mockRequest.error(error, { status: 403 ,statusText: 'Invalid access.'});

    mockHttp.verify();
}));

Upvotes: 0

clo5ure
clo5ure

Reputation: 90

I know I'm late to this but I thought I'd answer this anyway for anyone who stumbles upon this like I did. I find the complete lack of documentation on angular.io around this error case to be insane. "Figure it out yourself", I guess?

Anyway...the approach I took was to avoid .error() altogether since it didn't seem to be as easy as using .flush() and the documentation does state it can be used for both successful and unsuccessful responses.

Here's how I would update your code to use flush:

it('should format the error message', (done) => {
    const credentials = {userName: 'bob', password: '123'} as LoginPayload;
    const mockErrorResponse = {message: 'failed to login'} ;

    authService.login(credentials).subscribe(() => {}, err => {
        // NOTE: err.error.message
        expect(err.error.message).toEqual(mockErrorResponse.message);
        done();
    });

    const req = httpMock.expectOne('/api/auth/signin');
    req.flush({message: mockErrorResponse.message}, {status: 400, statusText: ''});

    httpMock.verify();
});

The one annoying part about this update with HttpClient in @angular/common/http as opposed to Http in @angular/http is that the error message is now a property of err.error in the subscribe() rather than err itself.

So if you're like me and you're upgrading from one to the other, all references to err.message must now be updated to err.error.message. I hope this helps.

Upvotes: 4

C&#233;dric Berlez
C&#233;dric Berlez

Reputation: 161

I have some suggestions which worked out for me.

To begin with I'd replace this:

authService = TestBed.get(AuthService);
httpMock = TestBed.get(HttpTestingController);

with this:

const testBed = getTestBed();
authService = testBed.get(AuthService);
httpMock = testBed.get(HttpTestingController);

I think the point here is that you fail to actually access the Testbed.

I'd also ad a verification that your request is a POST request with the following:

expect(req.request.method).toBe("POST");

These are the things I would add on first thought.

Upvotes: 0

Related Questions