How to test an error thrown from a subscribe
Asked Answered
R

1

6

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.

Resentful answered 1/11, 2018 at 21:49 Comment(4)
Your function, indeed, doesn't throw any error. It subscribes to an observable, and long after your function has returned, when the error http response finally comes back, the error callback passed when subscribing throws an error. What's the point of throwing this error? What do you expect to happen that wouldn't alredy happen by simply letting the original error propagate?Inconsequential
what do you mean by propagate ? propagate where? This is my component there is no upstream to this. In the full project there exists an angular app level error handler component which is meant to catch any unhandled error and gracefully handle that. That is why i am throwing the error.Resentful
This is my component there is no upstream to this.: hence my question: what's the point of throwing an error, instead of letting the original unhandled error propagate until it reached your application-level error handler? Instead of getting the original, detailed http error, your handle will only get a meaningless, cryptic error telling 'failed'. What's the point?Inconsequential
If anyone still encouter this issue please see #53110334Gardie
V
3

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

Vagabondage answered 2/11, 2018 at 21:6 Comment(1)
Thanks for the suggestion, I already tried that and it worked, But i am writing the tests and the code was originally written by someone else and I am not allowed to change the code. But thanks for the reply. Also i think in the code there is a generic angular errorhandler which is supposed to catch these errors and handle them gracefully.Resentful

© 2022 - 2024 — McMap. All rights reserved.