简体   繁体   中英

Test angular HttpInterceptor

The goal of this interceptor is to re-send the request when a captcha-key is required by the server.

But it could be use when a jwt token should be refreshed.

The interceptor works fine but I cannot explain why the test is failing.

The flow will never pass into the httpClient.get('/error').subscribe(), if the response code != 200.

Here is a link of a reproductible demo : https://stackblitz.com/edit/angular-testing-template-mfqwpj?embed=1&file=app/interceptor.spec.ts

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Injectable} from '@angular/core';
import {catchError, switchMap} from 'rxjs/operators';
import {CaptchaHeader, CaptchaV2Service} from 'century-lib';


@Injectable({
  providedIn: 'root'
})
export class CaptchaInterceptor implements HttpInterceptor {

  constructor(private captchaService: CaptchaV2Service) {
  }


  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      catchError(err => {
        if (!this.captchaIsRequired(err)) {
          return;
        }
        return this.captchaService.getCaptchaKey().pipe(
          switchMap((key) => {
            const newReq = this.applyCaptchaKey(req, key);
            return next.handle(newReq);
          })
        );
      })
    );
  }

  applyCaptchaKey(req, key) {
    return req.clone({
      headers: req.headers.set('Captcha-Token', key)
    });
  }

  private captchaIsRequired(error) {
    return (error.status === 400 && error.headers.get('Captcha-Status') === 'required');
  }

}

Test:

import {async, TestBed} from '@angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {CaptchaV2Service} from 'century-lib';
import {HTTP_INTERCEPTORS, HttpClient, HttpHeaders} from '@angular/common/http';
import {CaptchaInterceptor} from './captcha.interceptor';
import {EventEmitter} from '@angular/core';

class MockCaptchaService {
  valid = new EventEmitter<string>();
  reset = new EventEmitter<boolean>();

  getCaptchaKey() {
    setTimeout(() => {
      this.valid.emit('captcha-key');
    }, 500);
    return this.valid;
  }
}

describe('Captcha interceptor', () => {
  let httpClient: HttpClient;
  let httpMock: HttpTestingController;
  let interceptor: CaptchaInterceptor;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        CaptchaInterceptor,
        {provide: CaptchaV2Service, useValue: new MockCaptchaService()},
        {provide: HTTP_INTERCEPTORS, useClass: CaptchaInterceptor, multi: true},
      ]
    });

    httpClient = TestBed.get(HttpClient);
    httpMock = TestBed.get(HttpTestingController);
    interceptor = TestBed.get(CaptchaInterceptor);
  });


  it('should construct', async(() => {
    expect(interceptor).toBeDefined();
  }));

  it('Should interrogate the captchaService when service returns Captcha-Required', async(() => {
    httpClient.get('/error').subscribe(() => {
    }, () => {
    });
    const req = httpMock.expectOne('/error');
    req.error(new ErrorEvent('Captcha Error'), {
      status: 400,
      statusText: 'Captcha-Error',
      headers: new HttpHeaders().set('Captcha-Status', 'required')
    });
    expect(req.request.headers.get('Captcha-Token')).toBe('captcha-key');
    httpMock.verify();
  }));

  afterEach(() => {
    TestBed.resetTestingModule();
  });


});
const req = httpMock.expectOne('/error');
req.error(new ErrorEvent('Captcha Error'), {
  status: 400,
  statusText: 'Captcha-Error',
  headers: new HttpHeaders().set('Captcha-Status', 'required')
});
expect(req.request.headers.get('Captcha-Token')).toBe('captcha-key');

This does not make any sense. You have single request req and you flush it with error. That is fine, but at this point request is complete and nothing will (you had request and you got the response).

Now last line expect exact opposite - that completed request will somehow change.

This is not what your interceptor is doing. Interceptor is making another request to get new token (or validate captcha) and then it retries the original request. Remove expect and mock.verify() will show you all requests that have been made.

Here is my final test :

import {async, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {CaptchaV2Service} from 'century-lib';
import {HTTP_INTERCEPTORS, HttpClient, HttpHeaders} from '@angular/common/http';
import {CaptchaInterceptor} from './captcha.interceptor';
import {Observable} from 'rxjs';


function ObservableDelay<T>(val: T, delay: number, cb = () => {
}): Observable<any> {
  return new Observable(observer => {
    setTimeout(() => {
      observer.next(val);
      observer.complete();
      cb();
    }, delay);
  });
}

const CAPTCHA_TOKEN = 'captcha-token';

describe('Captcha interceptor', () => {
  let httpClient: HttpClient;
  let httpMock: HttpTestingController;
  let interceptor: CaptchaInterceptor;
  let captchaService: CaptchaV2Service;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        CaptchaInterceptor,
        {provide: CaptchaV2Service, useClass: CaptchaV2Service},
        {provide: HTTP_INTERCEPTORS, useClass: CaptchaInterceptor, multi: true},
      ]
    });

    httpClient = TestBed.get(HttpClient);
    httpMock = TestBed.get(HttpTestingController);
    interceptor = TestBed.get(CaptchaInterceptor);
    captchaService = TestBed.get(CaptchaV2Service);
  });


  it('should construct', async(() => {
    expect(interceptor).toBeDefined();
  }));

  it('Should interrogate the captchaService when service returns Captcha-Required', fakeAsync(() => {

    spyOn(captchaService, 'getCaptchaKey').and.returnValue(ObservableDelay(CAPTCHA_TOKEN, 200, () => {
      httpMock
        .expectOne(r => r.headers.has('Captcha-Token') && r.headers.get('Captcha-Token') === CAPTCHA_TOKEN);
    }));

    httpClient.get('/error').subscribe();
    const req = httpMock.expectOne('/error');
    req.error(new ErrorEvent('Captcha Error'), {
      status: 400,
      statusText: 'Captcha-Error',
      headers: new HttpHeaders().set('Captcha-Status', 'required')
    });

    tick(200);
  }));


});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM