Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test observable 'next' callback in RxJs and Angular

I'm trying to do a pretty simple test in Angular with RxJs Observables, but I'm coming up short. This is what I'm basically trying to test:

// We're inside some Angular component here...
let testMe = 0;

function somethingOrOther(): void {

    this.someService.methodReturningObservable()
      .subscribe(
          (nextValue: number): void => {
              testMe += nextValue;
          }
      ) 
}

How do I test that testMe gets updated properly when the observable behind methodReturningObservable emits a value?

I attempted it with this:

it(`works 'cuz I want it to`, fakeAsync(() => {
    spyOn(this.someService, 'methodReturningObservable').and.returnValue(cold('a', {a: 10}));

    tick();

    expect(component.testMe).toBe(10);
}));

So, tick() doesn't seem to do anything here. Nothing gets my cold value to ever emit on my spy.

I tried getTestScheduler.flush() instead as shown at https://netbasal.com/testing-observables-in-angular-a2dbbfaf5329 under the marbles section.

Can I use marbles like this to emit a value on an observable? This was very easy in AngularJS by just triggering a digest, but I can't seem to get Anguar to let me in the next callback for an observable.

like image 263
Spencer Avatar asked Oct 17 '22 09:10

Spencer


1 Answers

I put together a simple Stackblitz to show you how I would approach testing that simple component method you describe.

Here is the spec from that Stackblitz:

it(`works with "of" 'cuz I want it to`, () => {
    spyOn(someService, 'methodReturningObservable').and.returnValue(of(10));
    component.somethingOrOther();
    expect(component.testMe).toBe(10);
});

Some of the changes I made to your code:

  • I had to make minor changes to your component method to have it work. Details in the Stackblitz.
  • The cold() function is from the marble testing library, but that is way overkill for such a simple function. rxjs has the of Observable creation method available which can create a cold synchronous observable that can be subscribed to (but more below if you really want to test it this way).
  • Since the spec above uses a synchronous observable, there is no need for fakeAsync(), as it will complete immediately.
  • The method under test needed to be called explicitly to be tested, done above with the line component.somethingOrOther().

As you can see in the Stackblitz, this test passes just fine.

Now, if you want to use marble testing even for this simple case, I set up another spec in the Stackblitz for that. It is as follows:

it(`works with "cold" 'cuz I want it to`, () => {
    spyOn(someService, 'methodReturningObservable').and.returnValue(cold('a', { a: 10 }));
    component.somethingOrOther();
    getTestScheduler().flush(); // flush the observables
    expect(component.testMe).toBe(10);
});
  • Note the fact that the component function needed to be called
  • The flushing of the getTestScheduler needs to be done as well.

Both tests are now passing in the Stackblitz.

Official docs for marble testing in Angular are here.

I hope this helps.

like image 145
dmcgrandle Avatar answered Nov 04 '22 20:11

dmcgrandle