How to test an error thrown from a subscribe

Viewed 6854

I am testing the call function below in angular 4.

import { Component, OnInit } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title = 'jasmine-error-test';

  constructor(private appService: AppService) { }

  ngOnInit(): void {
    this.call();
  }

  call() {
    return this.appService.load().subscribe((res) => {
      this.title = res;
    }, (err) => {
      throw new Error('Failed');
    });
  }

}

To test the part where an error is thrown from the subscribe i am doing the following.

describe('when call is called', () => {
    describe('when the service returns an error', () => {
      let app;
      beforeEach(async(() => {
        const fixture = TestBed.createComponent(AppComponent);
        app = fixture.componentInstance;
        (service.load as jasmine.Spy).and.returnValue(Observable.throw({
          status: 406,
          error: {
            message: 'Test 406 error'
          }
        }));
      }));
      it('it should throw a  matching error', async(() => {
        expect(() => { app.call(); }).toThrowError('Failed');
      }));
    });
  });

But the test fails with and error

Expected function to throw an Error.

If i use the debugger window it show the line where the error is being thrown is being hit , but still i don't get the test to pass. Can somebody let me know what is going on.

1 Answers

Interesting question. Error handling with Observables is tricky. As you found out, re-throwing the error is non-trivial because the way to catch such an error is within a try/catch block, but that doesn't work with asynchronous code. :) There are many good discussions on the web about this, here is one I found: Error Handling in the Reactive Extensions.

I would suggest refactoring your code. If an error occurs you could catch the details in a component variable as you do with this.title already. Perhaps call it this.error, then you could test for it being null (no error), or having a value (in case of error). Perhaps refactor like this:

call() {
    return this.appService.load().subscribe((res) => {
        this.title = res;
    }, (err) => {
        this.error = err;
        // throw new Error('Failed');
    });
}

Then your test would look something like this:

it('it should capture any error in this.error', () => {
    // expect(() => { app.call(); }).toThrowError('Failed');
    app.call();
    expect(app.error).toEqual(/* testError */);
});

In case it is helpful to you, here is a stackblitz I put together to try out some ideas regarding this question: How to Test an Error Thrown from a Subscribe

Related