0

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.

Mark van Straten
  • 9,287
  • 3
  • 38
  • 57
Mat B.
  • 1
  • 1
  • 2
  • Have a look at this https://stackoverflow.com/questions/42732988/how-do-i-test-a-function-that-returns-an-observable-using-timed-intervals-in-rxj/42734681#42734681 – martin Jul 17 '17 at 08:09
  • Unrelated to testing, but those calls you are making to `Observable.create` are something of an antipattern. You might want to consider watching the following: https://egghead.io/courses/save-time-avoiding-common-mistakes-using-rxjs – cartant Jul 17 '17 at 08:25
  • the easiest way when you have many async operations is to use the done function, look for jasmine.done on this page https://angular.io/guide/testing – gropapa Jul 17 '17 at 08:42
  • in both the mock classes return some data instead of an `empty object` – Aravind Jul 17 '17 at 08:50

1 Answers1

3

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
    })
});
Mark van Straten
  • 9,287
  • 3
  • 38
  • 57