Exploring the functionality of retrying an HTTP request using the retry() method in combination

When testing an HTTP call error response with the HttpClientTestingModule, everything runs smoothly until I introduce a rxjs retry(2) to the HTTP call. Suddenly, the test starts complaining about finding an unexpected request:

Expected no open requests, found 1

The challenge now is figuring out 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 same error message as before.

Example in Action

To see it in action, you can check out this Stackblitz link

Answer №1

Exploring the essentials of Angular - HttpClient - retry() explains:

The RxJS library provides various retry operators that are worth delving into. The simplest one is known as retry() and it automatically resubscribes to a failed Observable a specified number of times. By resubscribing to the result of an HttpClient method call, the HTTP request is reissued.

In essence, every time you execute flush, it leaves behind an open request. To handle this, you simply need to repeat the request handling and flushing process based on the number of retries set for the service.

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' });
  }
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue: Incompatibility in metadata versions detected for module .../ngx-masonry/ngx-masonry.d.ts. Level 4 version identified, whereas level 3 version

When using ngx-masonry, I encountered the following error message- ERROR in Error: Metadata version mismatch for module .../ngx-masonry/ngx-masonry.d.ts, found version 4, expected 3 Specifications: Angular 4 ngx-masonry 1.1.4 ...

What is the method for specifying the type of a Custom Component in Typescript?

I have developed a custom React component called Title, which dynamically renders different HTML elements like h1, h2, h3, h4, h5, h6, span, or div based on the props provided to the component. Everything is working perfectly: No errors related to typescr ...

Angular - issue with getting window.screen.height in template

One issue I'm facing is when attempting to optimize a section of my template for mobile devices in landscape mode. TS: window = window; Template: <div [ngStyle]="{'height': window.innerHeight < window.innerWidth ? window.screen ...

Ways to determine the complete list of HTML properties that can be bound to

From what I gather, Angular attempts to treat each HTML DOM element as a component and enables binding to its properties. Looking at the HTMLDivElement class, I notice it inherits the textContent property from Node and the innerHTML property from Element, ...

After the initial test is executed, Jasmine's spy-on function proceeds to call the actual function

The issue arises when the second test fails due to an error message stating that "Cannot read property 'get' of undefined". This error occurs because the second test references the real service, which contains a private property called "http" tha ...

Is it possible for the ionic ionViewDidEnter to differentiate between pop and setRoot operations?

I am facing an issue with my ionic 3 page where I need to refresh the data on the page only if it is entered via a navCtrl.setRoot() and not when returned to from a navCtrl.pop(). I have been using ionViewDidEnter() to identify when the page is entered, bu ...

Import JSON data into Jasmine/Karma while running unit tests in AngularJS

I am currently testing a callback function that requires a response object as its sole parameter. The response object is the result of an HTTP request made from a different location, so using $httpBackend in this particular test is unnecessary as the reque ...

Click function for Angular bootstrap button

I have implemented a basic form in my template that already has validation. My main issue is how to check when the user clicks on the button after filling out all required fields? <section class="contact-area pb-80" style="margin-top:10%"> < ...

Numerous sentries summoning an asynchronous function

In my scenario, I have two guards - Guard1 and Guard2. Guard1 is responsible for returning an Observable whereas Guard2 returns a Boolean value. For canActivate: [Guard1, Guard2] If Guard2 were to return false, would the request made by Guard1 be automat ...

Deleting a parent item along with its child elements in the ngrx state management library

I am currently exploring ngrx store and grappling with where to place my logic, specifically whether looping through an array should be handled in the reducer or the component. I have an array of objects called Item that need to be manipulated - particular ...

Sending Data to Prestashop API: A Guide for Angular Developers

I've been working on a project using Angular that interacts with the API of Prestashop. Retrieving the list of products is easy because Prestashop provides it in JSON format. Now, I need to create data (POST data) and Prestashop requires XML. They o ...

What is the best way to include a TypeScript property within a JavaScript code?

I am currently attempting to utilize a typescript property known as this.data with the assistance of the executescript() method from the InAppBrowser plugin. However, I am encountering an issue where the property is returning undefined instead of 'tes ...

MongoDB Dynamic Match Query

Is there a way to dynamically write expressions using a variable in Node.js or TypeScript? $match: { date: '9-8-2019', "info.tag": "Test" } Or is it possible to do something like this: $match: { $expr: { ...

Secure method of utilizing key remapped combined type of functions

Imagine having a union type called Action, which is discriminated on a single field @type, defined as follows: interface Sum { '@type': 'sum' a: number b: number } interface Square { '@type': 'square&apos ...

An instance of an abstract class in DI, using Angular version 5

I have multiple components that require three services to be injected simultaneously with the same instance. After that, I need to create a new instance of my class for injecting the services repeatedly. My initial idea was to design an abstract class and ...

Angular: Real-time monitoring of changes in the attribute value of a modal dialog and applying or removing a class to the element

I cannot seem to figure out a solution for the following issue: I have two sibling div elements. The second one contains a button that triggers a modal dialog with a dark overlay. However, in my case, the first div appears on top of the modal dialog due to ...

An error occurred in the code while working with a React functional component defined as an arrow function

I am encountering an issue with the code below that is producing this error message: Type '() => void' is not assignable to type 'FC<{}>'. Type 'void' is not assignable to type 'ReactElement<any, any> | nul ...

Issue with finding module in TypeScript component import during Jest test

Despite having the correct path to my styled objects, I'm puzzled by the error message that has popped up: Error: Cannot find module '../../shared/models' from 'Astronaut.tsx' import { moonHoldings } from '../../shared/models ...

Tips for resolving the unmounted component issue in React hooks

Any suggestions on resolving this issue: Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect ...

Consequences of eliminating Angular component wrapper tags from the DOM

Imagine there is a component named <hello-world> with the following HTML template: <div>Hello World!</div> When this component is used and inspected in the DOM, it shows up like this: <hello-world> <div>Hello World!</div ...