0
votes
it('minimal test case', () => {
    expect(() => {
        of(1).pipe(
            map(() => {
                throw new Error('err');
            }),
            catchError(() => {
                throw new Error('new err');
            }),
        ).subscribe();
    }).toThrow();
});

This code will actually crash the whole execution of Jasmine/Karma, even in the original Error stacktrace leads to the Error inside catchError.

I think that if observable throws and cannot handle it should propagate that error in the context it is in. Otherwise, I cannot test if the observable throws.

This (Jasmine) test will produce following errors:

Uncaught Error: new err at CatchSubscriber.selector (slideshow.directive.spec.ts:224) at CatchSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/catchError.js.CatchSubscriber.error (catchError.js:34) at MapSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:38) at MapSubscriber.push.../../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53) at Observable._subscribe (subscribeToArray.js:5) at Observable.push.../../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:43) at Observable.push.../../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:29) at MapOperator.push.../../node_modules/rxjs/_esm5/internal/operators/map.js.MapOperator.call (map.js:18) at Observable.push.../../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:24) at CatchOperator.push.../../node_modules/rxjs/_esm5/internal/operators/catchError.js.CatchOperator.call (catchError.js:18)

TypeError: Cannot read property 'error' of undefined at directCallParentKarmaMethod (context.js:270) at ContextKarma.error (context.js:155) at handleGlobalErrors (adapter.js:176) at KarmaReporter.suiteDone (adapter.js:224) at dispatch (jasmine.js:4560) at ReportDispatcher.suiteDone (jasmine.js:4531) at nodeComplete (jasmine.js:1019) at onComplete (jasmine.js:5528) at ZoneDelegate.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423) at Zone.../../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:195)

Expected function to throw an exception. at UserContext. (http://localhost:9876/src/app/shared/thumbnail/slideshow.directive.spec.ts?:227:6) at ZoneDelegate.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9876/C:/Users/uzivatel/Documents/nubium/ulozto-web/angular/node_modules/zone.js/dist/zone.js?:391:1) at ProxyZoneSpec.push.../../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/C:/Users/uzivatel/Documents/nubium/ulozto-web/angular/node_modules/zone.js/dist/zone-testing.js?:289:1) at ZoneDelegate.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9876/C:/Users/uzivatel/Documents/nubium/ulozto-web/angular/node_modules/zone.js/dist/zone.js?:390:1)

1
Your explanation of what is happening is a bit unclear to me. Could you try to explain a little more clearly what is happening? What do you mean by "crashes" and what is causing the "crash". Are you saying the toThrow() is causing the exceptions or the use of catchError()? Some clarification would help.Narm
@Narm Edited question. But the gist is that even if it is called in expect().toThrow() it will crash Jasmine/Karma and that the stack trace of errors is weird.Akxe
Most of the observables are asynchronous. So throwing from a subscription callback can't possibly propagate to the current context. You can test if an observable throws by subscribing with an error callback, and check that the callback was indeed called.JB Nizet
Just like with promises, there’s no way to propagate the exception to the original context. Don’t think of it as an exception. Think of it as an “error result”. Test said result accordingly.cwharris
@cwharris Make it into an answer and I'll accept is, as it was the answer to my question.Akxe

1 Answers

1
votes

One possible solution is to redirect the error to the success path, then testing that value. This can be useful when the stream under test has logic connected to throwing different error types (though in such a case a test for instanceof Error would of course not be sufficient).

  it('should be of type error', () => {
    of(1)
      .pipe(
        map(() => {
          throw new Error('err');
        }),
        catchError(error => of(error)))
      .subscribe(result => {
        expect(result instanceof Error).toBe(true);
      });
  });

But a more common approach afaik is to test if the correct callback has been called, as mentioned by JB Nizet. E.g. your service has an handleError method that should be called when a stream errors.