3

In my Angular application, I was using Observables in the following way:

getItem(id): Observable<Object> {
  return this.myApi.myMethod(...); // returns an Observable
}

and I unit tested it:

it('getItem(...) should correctly do its job',
  inject([MyService], (service: MyService) => {

    const spy = spyOn(myApi, 'myMethod').and.returnValue(mockObservable);

    const mockObservable = Observable.of('mock');

    expect(service.getItem(123)).toEqual(mockObservable);

    expect(spy).toHaveBeenCalledWith(...);
  })
);

and it worked perfectly fine.

However if I try to add a default error-handling logic to my getItem() method using catch:

getItem(id): Observable<Object> {
  return this.myApi.myMethod(...).catch(e => {
    /* display a notification error ... */
    return Observable.throw(e);
  });
}

it still works fine but now the test are failing, saying:

Expected object to be a kind of ScalarObservable, but was Observable({ _isScalar: false, source: ScalarObservable({ _isScalar: true, value: 'mock', scheduler: null }), operator: CatchOperator({ selector: Function, caught: }) }).

what does it mean? why does it happen? how can I solve this issue?

Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
  • `catch` is an operator, so it returns an new observable with the observable you were expecting as it's `source`. That's how operators are implemented. – cartant Nov 24 '17 at 23:03
  • 1
    To expand on @cartant 's comment. The check `expect(service.getItem(123)).toEqual(mockObservable)` fails because they are not the same object. The service method is returnning a new observable that wraps `mockObservable` and so the check fails. Your test would be better off subscribing to the observable returned from your service call and ensuring it returns `'mock'`. – Pace Nov 26 '17 at 19:08
  • thanks @Pace, this actually solves my issue. – Francesco Borzi Nov 26 '17 at 19:32

1 Answers1

1

thanks to @Pace's input:

expect(service.getItem(123)).toEqual(mockObservable) fails because they are not the same object. The service method is returnning a new observable that wraps mockObservable and so the check fails. Your test would be better off subscribing to the observable returned from your service call and ensuring it returns 'mock'

I found the solution:

it('getItem(...) should correctly do its job',
  inject([MyService], (service: MyService) => {

    const spy = spyOn(myApi, 'myMethod').and.returnValue(mockObservable);

    const mockData = 'mock'; // can be anything
    const mockObservable = Observable.of(mockData);


    service.getItems().subscribe((data) => {
      expect(data).toEqual(mockData);
    });

    expect(spy).toHaveBeenCalledWith(...);
  })
);
Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
  • 2
    Just be careful with that sort of test. If the observable returned from `getItems` is asynchronous then that `expect` may never get called. For example, you could change `getItems` to be `this.myApi.myMethod(...).map(() => 'not-mock').delay(500);` and the test will still pass. One workaround is to change the `expect` to look like...`return service.getItems().do(() => { expect(data).toEqual(mockData); }).toPromise()` and use something like `jasmine-promise`. There are other ways to do that as well. – Pace Nov 27 '17 at 16:03