Search code examples
angularpromiserxjsangular-test

angular: httpClient.get from resolved Promise fails from test


I'm having a hard time writing a test for a service which does a call to angular's httpClient.get(...) from inside the then of a resolved promise. Inside of the whole app (i.e. not the test) everything is working as expected, the promises get resolved and from the resolved promise's data the URL is extracted for the httpClient.get call

However in the test even though the promise is resolved (the then(...) is executed) the httpClient.get(...) is never executed apparently.

To illustrate the problem I created a snipped based on angular's Http Guide Testing. See the whole thing here: https://stackblitz.com/edit/angular-y21e6j

The test with the promise is failing with:

Error: Expected one matching request for criteria "Match URL: api/heroes", found none.

In general I have two functions:

  getHeroes(): Observable<any> {
    const sub = new Subject();
    this.http.get<any>(this.heroesUrl)
      .pipe(
      catchError(this.handleError('getHeroes', []))
      ).subscribe(data => sub.next(data));
    return sub;
  }


  notWorking(): Observable<any> {
    const sub = new Subject();
    const promise = Promise.resolve([this.heroesUrl]);
    promise.then(url => {
      console.log('Promise is resolved');
      this.http.get<any>(url[0])
        .pipe(
        catchError(this.handleError('getHeroes', []))
        ).subscribe(data => sub.next(data));
    })
    return sub;
  }

I also copied the tests from the angular guide and inserted one for the second method. They look like this:

it('should return expected heroes (called once)', () => {

  heroService.getHeroes().subscribe(
    heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
    fail
  );

  // HeroService should have made one request to GET heroes from expected URL
  const req = httpTestingController.expectOne(heroService.heroesUrl);
  expect(req.request.method).toEqual('GET');

  // Respond with the mock heroes
  req.flush(expectedHeroes);
});

    it('should also work with promise', () => {

  heroService.notWorking().subscribe(
    heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
    fail
  );

  // HeroService should have made one request to GET heroes from expected URL
  const req = httpTestingController.expectOne(heroService.heroesUrl);
  expect(req.request.method).toEqual('GET');

  // Respond with the mock heroes
  req.flush(expectedHeroes);
});

Notice that as soon as you remove the promise.then from the notWorking() the test succeeds again.

I currently cannot work around the additional subject created there, but this shouldnt influence the issue I have with the promise.

I also can't work around the promise, because it's returned from a 3rd party library. I tried wrapping it to a Observable (fromPromise) but this didnt help either.

See the whole thing here: https://stackblitz.com/edit/angular-y21e6j


Solution

  • with some help I found the issue... According to https://www.joshmorony.com/testing-asynchronous-code-with-fakeasync-in-angular fakeAsync() along with flushMicroTasks() needs to be used... And indeed it does work:

    it('should also work with promise and fakeAsync', fakeAsync(() => {
    
          heroService.notWorking().subscribe(
            heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
            fail
          );
          flushMicrotasks();
    
          // HeroService should have made one request to GET heroes from expected URL
          const req = httpTestingController.expectOne(heroService.heroesUrl);
          expect(req.request.method).toEqual('GET');
    
          // Respond with the mock heroes
          req.flush(expectedHeroes);
        }));