Search code examples
angularangular-test

HttpTestingController does not match a request because my HttpInterceptor processes a Promise


I want to test an HttpInterceptor which calls an asynchronous function and adds the result to the header of the request. Unfortunately, I get the error:

Error: Expected one matching request for criteria "Match URL: test", found none.

I broke the cause of the error down to a function returning a Promise which is processed by the HttpInterceptor. Strangely, if the HttpInterceptor processes an Observable, everything works. Therefore, I wrote two HttpInterceptors processing a Promise and an Observable which should do the same thing. The tests with the ObservableHttpInterceptor work and the tests with the PromiseHttpIntercetor do not work.

import { TestBed } from '@angular/core/testing';
import {
  HTTP_INTERCEPTORS,
  HttpClient,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { Injectable } from '@angular/core';
import { BehaviorSubject, from, mergeMap, Observable } from 'rxjs';

describe('Interceptor', () => {
  const TEST_API = 'test';

  it('works', () => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: ObservableInterceptor,
          multi: true,
        },
      ],
    });
    const http = TestBed.inject(HttpClient);
    const httpTestingController = TestBed.inject(HttpTestingController);

    http.get(TEST_API).subscribe(value => console.log(value));
    const request = httpTestingController.expectOne(TEST_API);
    expect(request.request).toBeTruthy();
  });

  it('does not work', () => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: PromiseInterceptor,
          multi: true,
        },
      ],
    });
    const http = TestBed.inject(HttpClient);
    const httpTestingController = TestBed.inject(HttpTestingController);

    http.get(TEST_API).subscribe(value => console.log(value));
    const request = httpTestingController.expectOne(TEST_API);
    expect(request.request).toBeTruthy();
  });
});

@Injectable()
export class PromiseInterceptor implements HttpInterceptor {
  public intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return from(Promise.resolve(true)).pipe(mergeMap(bool => next.handle(request)));
  }
}

@Injectable()
export class ObservableInterceptor implements HttpInterceptor {
  public intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return new BehaviorSubject(true).pipe(mergeMap(bool => next.handle(request)));
  }
}

Can someone tell me the difference between the two Interceptors and how I can implement an HttpInterceptor processing a Promise function that has working tests?


Solution

  • I had to wrap the test function in fakeAsync and use tick() after calling the HttpClient. This simulates passing of time so that the Promise in the HttpInterceptor is resolved before the HttpTestingController is called.

    it('does not work', fakeAsync(() => {
      // ...
    
      http.get(TEST_API).subscribe(value => console.log(value));
      tick();
      const request = httpTestingController.expectOne(TEST_API);
      expect(request.request).toBeTruthy();
    }));