rxjs how to expect an observable to throw error
Asked Answered
H

5

14

In my TypeScript app I have a method that return an rxjs Observable which, in a certain case, can return throwError:

import { throwError } from 'rxjs';

// ...

getSomeData(inputValue): Observable<string> {
  if (!inputValue) {
    return throwError('Missing inputValue!');
  }

  // ...
}

how can I write a test to cover this specific case?

Hovercraft answered 5/9, 2019 at 11:41 Comment(0)
T
11

You can test it using RxJS Marble diagram tests. Here's how:

const getSomeData = (inputValue: string): Observable<string> => {
  if (!inputValue) {
    return throwError('Missing inputValue!');
  }

  // e.g.
  return of(inputValue);
};

describe('Error test', () => {

  let scheduler: TestScheduler;

  beforeEach(() => {
    scheduler = new TestScheduler((actual, expected) => {
      expect(actual).toEqual(expected);
    });
  });

  it('should throw an error if an invalid value has been sent', () => {
    scheduler.run(({ expectObservable }) => {

      const expectedMarbles = '#'; // # indicates an error terminal event

      const result$ = getSomeData(''); // an empty string is falsy

      expectObservable(result$).toBe(expectedMarbles, null, 'Missing inputValue!');
    });
  });

  it('should emit an inputValue and immediately complete', () => {
    scheduler.run(({ expectObservable }) => {

      const expectedMarbles = '(a|)';

      const result$ = getSomeData('Some valid string');

      expectObservable(result$).toBe(expectedMarbles, { a: 'Some valid string' });
    });
  });
});

For more info on how to write these tests, please take a look at this link.

Trematode answered 5/9, 2019 at 11:59 Comment(0)
A
6

I imagine your full case resembles something like this

// first there is something that emits an Observable
export function doSomethingThatReturnsAnObservable() {
  return createSomehowFirstObservable()
  .pipe(
     // then you take the data emitted by the first Observable 
     // and try to do something else which will emit another Observable
     // therefore you have to use an operator like concatMap or switchMap
     // this something else is where your error condition can occur
     // and it is where we use your getSomeData() function
     switchMap(inputValue => getSomeData(inputValue))
  );
}
}

// eventually, somewhere else, you subscribe
doSomethingThatReturnsAnObservable()
.subscribe(
   data => doStuff(data),
   error => handleError(error),
   () => doSomethingWhenCompleted()
)

A test for the error condition could look something like this

it('test error condition'), done => {
   // create the context so that the call to the code generates an error condition
   .....
   doSomethingThatReturnsAnObservable()
   .subscribe(
      null, // you are not interested in the case something is emitted
      error => {
        expect(error).to.equal(....);
        done();
      },
      () => {
        // this code should not be executed since an error condition is expected
        done('Error, the Observable is expected to error and not complete');
      }
   )
})
Amytal answered 5/9, 2019 at 13:43 Comment(0)
C
2

Another way is to check that the operator thows an error is to pipe it like below. I assume that you are using Chai.

import { catchError } from 'rxjs/operators';

it('must throw an error',  done => {
     getSomeData().pipe(catchError((e) => [e])).subscribe(e => {
        expect(e).to.be.an.instanceof(Error);
        done();
      })
})

Source How to test an RxJS operation that throws an error

Cropland answered 28/12, 2020 at 20:42 Comment(0)
H
2

I bumped into my own old question and solved it this way:

it('should throw error if ...', (done) => {
  const { service } = setup();

  service.myObs$().subscribe({
    error: (err) => {
      expect(err).toEqual(new Error(`Some error`))
      done();
    },
  });

  // some code making myObs$ throwing a new error
});
Hovercraft answered 2/11, 2022 at 15:38 Comment(0)
I
1

In addition to lagoman's answer. You could simplify the way to get the testScheduler.

describe('Error test', () => {  
  it('should throw an error if an invalid value has been sent', () => {
    getTestScheduler().run(({ expectObservable }) => { // getTestScheduler from jasmine-marbles package

      const expectedMarbles = '#'; // # indicates an error terminal event

      const result$ = getSomeData(''); // an empty string is falsy

      expectObservable(result$).toBe(expectedMarbles, null, 'Missing inputValue!');
    });
  });

  it('should emit an inputValue and immediately complete', () => {
    getTestScheduler().run(({ expectObservable }) => {

      const expectedMarbles = '(a|)';

      const result$ = getSomeData('Some valid string');

      expectObservable(result$).toBe(expectedMarbles, { a: 'Some valid string' });
    });
  });
});
Indeclinable answered 31/1, 2020 at 9:25 Comment(2)
Hi @davidvdijk, thank you for your effort. I have to add a note here: jasmine-marbles is a completely different library that does not have to be used nor it is used in my answer. I wouldn't even recommend using the library - I've had so many problems using it, especially with time progression, so I'd still stick with the official and recommended way of writing RxJS unit tests.Trematode
Thanks for commenting @lagoman, I've not yet experienced problems with the package, but will look into it. A little off-topic though, but imo creating a new testScheduler each test is a bit redundant and may take more time than re-using a test scheduler. But again, i've to look into that. My answer was just an addition/alternative to yours, for the ones using jasmine-marbles (:Indeclinable

© 2022 - 2024 — McMap. All rights reserved.