Mat B.
Mat B.

Reputation: 1

Angular2 / RxJS / Jasmine : How to test Observable chain/sequence (operators)

As part of an Angular 4 project, I am desperately trying to test with Jasmine a function which implement an RxJs chain/sequence with operators (map in my case).

class RendezVousResolver {

  searchRendezVous(code: string): Observable<any> {
    return Observable.create(observer => {
      this.userCardService.readCard(code).map(userData => {
        this.rendezVousService.search(userData).subscribe(
          result => {
            observer.next(result);
          },
          error => {
            observer.error(error);
          }
        );
      });
    });
  }

}

My unit test uses 2 mocks in order to "simulate" the 2 services' layers: userCardService and rendezVousService.

class MockUserCardService {

  readCard(code: string): Observable<any> {
    return Observable.of('<data></data>');
  }

}

class MockRendezVousService {

  search(userData : string): Observable<any> {
    return Observable.of({
      rdvs: []
    });
  }

}

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      RendezVousResolver,
      { provide: RendezVousService, useClass: MockRendezVousService },
      { provide: SwcVitaleReadingService, useClass: MockSwcVitaleReadingService }
    ]
  });
  fixture = TestBed.get(RendezVousResolver);
});

And here my unit test.

it('should return the expected response', async(() => {
  fixture.resolve(undefined, undefined).subscribe(
    rdvs => {
      console.log("expect");
      expect(rdvs).toEqual({
        rdvs: []
      });
    },
    error => {
      console.log("fail");
      fail('No error was expected');
    }
  );
}));

When I execute it, the test seems to not wait for the events emitted by the mocked Observable. Neither the expected nor the fail are executed. I'm sure about that because nothing is logged in the console.

The only way I found to make this test passed is to not use the operator map and replace my code with nested subscriptions.

searchRendezVous(code: string): Observable<any> {
  return Observable.create(observer => {
    this.userCardService.readCard(code).subscribe(
      userData => {
        this.rendezVousService.search(userData).subscribe(
          rdvs => {
            observer.next(rdvs);
          },
          error => {
            observer.error(error);
          }
        )
      },
      error => {
        observer.error(error);
      }
    );
  });
}

I encountered the same problem with others operators than map (zip for example).

Thank you for your help.

Upvotes: 0

Views: 5445

Answers (1)

Mark van Straten
Mark van Straten

Reputation: 9425

You can simplify your RendezVousResolver by swapping Observable.create for existing behaviour / functions in RxJs:

class RendezVousResolver {
  searchRendezVous(code: string): Observable<any> {
    return this.userCardService.readCard(code)
      .mergeMap(userData => this.rendezVousService.search(userData));
  }
}

That way you have less edge cases to catch yourself.

Testing this can be done without time by swapping the readCard and search with mocks returning a Rx.Observable.from([]) with the expected mock data. Simply invoking .toPromise() on your searchRendezVous() will make that work without any scheduler magic.

it('returns data', () => {
  return searchRendezVous('foo')
    .toPromise()
    .then(searchResults => {
      expect(searchResults).to.not.be.empty();//assert what you need
    })
});

Upvotes: 3

Related Questions