Testing an HTTP request with retry() and HttpClientTestingModule
Asked Answered
L

1

9

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

Letha answered 22/3, 2018 at 21:57 Comment(6)
I think the retry has delay and your test evaluates it before thatBetoken
Take a look at this page facebook.github.io/jest/docs/en/asynchronous.htmlHillel
@Betoken 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...Letha
@Vayrex. Thanks, but I've already tried async, done,... no success. :-(Letha
Try to return a promise, async expects something thenable.Hillel
@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 calledBetoken
T
15

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' });
  }
});
Tocsin answered 26/3, 2018 at 20:14 Comment(3)
It was actually just the + 1 that was missing. How silly! Thanks a lot! :-)Letha
I figured out myself just before I read this. Still only thing I could find on it so it's a good answer. UpvotedWelloiled
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.'Thallium

© 2022 - 2024 — McMap. All rights reserved.