测试角度HttpInterceptor

时间:2018-09-11 11:58:42

标签: angular unit-testing karma-jasmine angular-http-interceptors

此拦截器的目标是在服务器需要验证码时重新发送请求。

但是当刷新jwt令牌时可以使用它。

拦截器工作正常,但我无法解释测试失败的原因。

如果响应代码!= 200,该流将永远不会传递到httpClient.get('/ error')。subscribe()中。

以下是可复制演示的链接:  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');
  }

}

测试:

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


});

2 个答案:

答案 0 :(得分:1)

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

这没有任何意义。您有单个请求req,并且错误地刷新了它。很好,但是此时请求已完成,不会有任何结果(您有请求,但得到了响应)。

现在最后一行期望完全相反-完成的请求将以某种方式改变。

这不是您的拦截器正在执行的操作。拦截器正在提出另一个请求以获取新令牌(或验证验证码),然后重试原始请求。删除expectmock.verify()将向您显示所有已提出的请求。

答案 1 :(得分:0)

这是我的最终考试:

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


});