7

I want to test an HTTP call error response with the HttpClientTestingModule. This works fine until I add a rxjs retry(2) to the HTTP call. Then, the test obviously complains that an unexpected request is found:

Expected no open requests, found 1

But now, I don't know how to expect two requests using the HttpTestingController:

service.ts

@Injectable()
export class Service {
  constructor(private http: HttpClient) { }

  get() {
    return this.http.get<any>('URL')
      .pipe(
        retry(2),
        catchError(error => of(null))
      )
  }
}

service.spec.ts

describe('Service', () => {
  let httpTestingController: HttpTestingController;
  let service: Service;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [Service],
      imports: [HttpClientTestingModule]
    });

    httpTestingController = TestBed.get(HttpTestingController);
    service = TestBed.get(Service);
  });

  afterEach(() => {
    httpTestingController.verify();
  });

  it('should handle 404 with retry', () => {
    service.get().subscribe((data: any) => {
      expect(data).toBe(null);
    });
    expectErrorResponse(404);
  });

  function expectErrorResponse(status: number) {
    const requests = httpTestingController.match('URL');
    // fails -> finds only one request
    expect(requests.length).toBe(2);
    requests.forEach(req => req.flush('error', {status, statusText: 'Not Found'}));
  }
});

If I remove the expect(requests.length).toBe(2), the test will fail with the error message from before.

Running Example

You can try it out with this Stackblitz

Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • 1
    I think the retry has delay and your test evaluates it before that – Yaser Mar 22 '18 at 22:15
  • 1
    Take a look at this page https://facebook.github.io/jest/docs/en/asynchronous.html – Vayrex Mar 22 '18 at 22:25
  • @Yaser Yes, I think so too. But I don't know what I should wait for. I was surprised that it works without the retry(). I thought I would run into problems when expecting within the `subscribe`, but that works... – Kim Kern Mar 22 '18 at 22:30
  • @Vayrex. Thanks, but I've already tried `async`, `done`,... no success. :-( – Kim Kern Mar 22 '18 at 22:30
  • Try to return a promise, async expects something thenable. – Vayrex Mar 22 '18 at 22:34
  • @KimKern I think the way you're testing it would make it harder, I would use a spy and check how many times it has been called – Yaser Mar 22 '18 at 22:57

1 Answers1

11

The fundamentals of Angular - HttpClient - retry() states:

The RxJS library offers several retry operators that are worth exploring. The simplest is called retry() and it automatically re-subscribes to a failed Observable a specified number of times. Re-subscribing to the result of an HttpClient method call has the effect of reissuing the HTTP request.

So every time you call flush it leaves behind an open request. You just need to repeat the request handling and flushing as many times as the service retries the request.

it('can test for 404 error', () => {
  const emsg = 'deliberate 404 error';

  testService.getData().subscribe(
    data => fail('should have failed with the 404 error'),
    (error: HttpErrorResponse) => {
      expect(error.status).toEqual(404, 'status');
      expect(error.error).toEqual(emsg, 'message');
    }
  );

  const retryCount = 3;
  for (var i = 0, c = retryCount + 1; i < c; i++) {
    let req = httpTestingController.expectOne(testUrl);
    req.flush(emsg, { status: 404, statusText: 'Not Found' });
  }
});
gaborb
  • 126
  • 1
  • 3
  • It was actually just the `+ 1` that was missing. How silly! Thanks a lot! :-) – Kim Kern Mar 26 '18 at 20:29
  • I figured out myself just before I read this. Still only thing I could find on it so it's a good answer. Upvoted – L1ghtk3ira Nov 28 '18 at 21:50
  • One thing... you basically HAVE to do it exactly like that. If you try to to two "expectOne" in a row and then do the req.flush, the second expectOne fails with an error of 'Error: Cannot flush a cancelled request.' – Reginald Blue Sep 19 '19 at 14:37