简体   繁体   English

使用“TestHttpInterceptor”测试 Angular 8 服务?

[英]Testing Angular 8 Services with `TestHttpInterceptor`?

I have a fairly small Angular front-end, which gets a lot of it's data from an external server.我有一个相当小的 Angular 前端,它从外部服务器获取大量数据。

Tests are giving my gyp测试给了我gyp

I can test a simple component by mocking the service, however this is a blanket "replace the output" solution, and not a real test... for that, I believe I need to provide a known return when the service calls the external API.我可以嘲笑服务测试一个简单的组件,然而,这是一种全面的“替换输出”的解决方案,而不是一个真正的考验......对于这一点,我相信我需要提供一个已知的回报当服务调用外部API .

Here's a simple example:这是一个简单的例子:

The object/interface definition:对象/接口定义:

// alerts.ts
export interface Alert {
  id: number;
  display_message: string;
  is_enabled: boolean;
}

The service definition:服务定义:

// alerts.service.ts
import { of as observableOf, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Alert } from './alerts';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

export interface IAlertService {
  getAlerts(): Observable<Alert[] | null>;
}

@Injectable()
export class AlertService implements IAlertService {

  readonly baseUrl = '/api/alerts/';
  alerts$: Observable<Alert[] | null>;

  constructor(private _http: HttpClient) { }

  getAlerts(): Observable<Alert[] | null> {
    return this._http.get<Alert[] | null>(this.baseUrl).pipe(
      catchError(error => {
        console.log('in catch: ', error);
        return observableOf(null);
      }));
  }
}

The component code:组件代码:

// alerts/alerts.component.html
<div *ngIf="alerts" >
  <div class="service-notice" *ngFor="let alert of alerts">
    <p [innerHTML]="alert.display_message"></p>
  </div>
</div>

and

// alerts/alerts.component.ts
import { Component, OnInit } from '@angular/core';

import { Alert } from '../alerts';
import { AlertService } from '../alerts.service';

@Component({
  selector: 'naas-alerts',
  templateUrl: './alerts.component.html',
  styleUrls: ['./alerts.component.scss'],
})
export class AlertComponent implements OnInit {

  alerts: Alert[] | null;

  constructor(private alertService: AlertService) { }

  ngOnInit() {
    this.alertService.getAlerts().subscribe(data => {
      this.alerts = data;
    });
  }
}

I then wrote an httpInterceptor class so I could define the string I wanted returned within the test:然后我写了一个 httpInterceptor 类,这样我就可以定义我想要在测试中返回的字符串:

// testing_interceptors.ts
import {
    HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HTTP_INTERCEPTORS
  } from '@angular/common/http';
import { Injectable, Injector } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';

@Injectable()
export class TestHttpInterceptor implements HttpInterceptor {

  current_containers: string = '[]';
  current_user: string = '';
  alerts: string = '[]';

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('TestHttpInterceptor called');
    const url_regex = /(\/api(?:\/[\w+\/]+)+)$/;
    const url_path = url_regex.exec(request.url)[1];

    if(request.method === "GET") {
        if(url_path == '/api/alerts/') {
            return observableOf(new HttpResponse({
                status: 200,
                body: this.alerts
            }));
        }
        if(url_path == '/api/users/current/') {
            return observableOf(new HttpResponse({
                status: 200,
                body: this.current_user
            }));
        }
        if (url_path === '/api/users/current/containers/') {
            return observableOf(new HttpResponse({
                status: 200,
                body: this.current_containers
            }));
        }
    }
  }
}

... and my test (yes, I've still got some of the old _component_mock_ commented out): ...和我的测试(是的,我仍然有一些旧的 _component_mock_ 注释掉):

// alerts/alerts.component.spec.test
import { HttpClientModule, HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { TestBed, ComponentFixture } from '@angular/core/testing';

import { AlertComponent } from './alerts.component';
import { AlertService } from '../alerts.service';
import { AlertServiceMock } from '../alerts.service.mock';

import { TestHttpInterceptor } from '../testing_interceptors';

describe('AlertsComponent', () => {
  let fixture: ComponentFixture<AlertComponent>;
  let component: AlertComponent;
  // let alertServiceMock: AlertServiceMock;
  let testHttpInterceptor: TestHttpInterceptor;

  beforeEach(() => {
    // alertServiceMock = new AlertServiceMock();
    testHttpInterceptor = new TestHttpInterceptor();

    TestBed.configureTestingModule({
      declarations: [ AlertComponent ],
      providers: [
        {provide: AlertService, useClass: AlertService },
        {provide: HttpClient, useClass: HttpClientModule},
        {provide: HTTP_INTERCEPTORS, useClass: TestHttpInterceptor, multi: true }
      ],
    }).compileComponents();
    fixture = TestBed.createComponent(AlertComponent);
    component = fixture.componentInstance;
  });

  it('should be created', done => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
    done();
  });

  // it('should have no alerts with no data', () => {
  //   alertServiceMock.test_alert = null;
  //   fixture.detectChanges();
  //   const compiled = fixture.debugElement.queryAll(By.css('p'));
  //   expect(compiled.length).toBe(0);
  // });

  // it('should have one alert', () => {
  //   alertServiceMock.test_alert = [{
  //     id: 1,
  //     display_message: 'Foo',
  //     is_enabled: true,
  //   }];
  //   fixture.detectChanges();
  //   const compiled = fixture.debugElement.queryAll(By.css('p'));
  //   expect(compiled.length).toBe(1);
  // });
});

The problem is, when I run this, I get the following error:问题是,当我运行它时,出现以下错误:

    TypeError: this.http.get is not a function
    error properties: Object({ ngDebugContext: DebugContext_({ view: Object({ def: Object({ factory: Function, nodeFlags: 33669121, rootNodeFlags: 33554433, nodeMatchedQueries: 0, flags: 0, nodes: [ Object({ nodeIndex: 0, parent: null, renderParent: null, bindingIndex: 0, outputIndex: 0, checkIndex: 0, flags: 33554433, childFlags: 114688, directChildFlags: 114688, childMatchedQueries: 0, matchedQueries: Object({  }), matchedQueryIds: 0, references: Object({  }), ngContentIndex: null, childCount: 1, bindings: [  ], bindingFlags: 0, outputs: [  ], element: Object({ ns: '', name: 'naas-alerts', attrs: [  ], template: null, componentProvider: Object({ nodeIndex: 1, parent: <circular reference: Object>, renderParent: <circular reference: Object>, bindingIndex: 0, outputIndex: 0, checkIndex: 1, flags: 114688, childFlags: 0, directChildFlags: 0, childMatchedQueries: 0, matchedQueries: Object, matchedQueryIds: 0, references: Object, ngContentIndex: -1, childCount: 0, bindings: Array, bindingFlags: 0, outputs: Array ...
        at <Jasmine>
        at AlertService.getAlerts (http://localhost:9876/_karma_webpack_/src/app/alerts.service.ts:20:22)
        at AlertComponent.ngOnInit (http://localhost:9876/_karma_webpack_/src/app/alerts/alerts.component.ts:18:23)
        at checkAndUpdateDirectiveInline (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:31910:1)
        at checkAndUpdateNodeInline (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:44367:1)
        at checkAndUpdateNode (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:44306:1)
        at debugCheckAndUpdateNode (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45328:36)
        at debugCheckDirectivesFn (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45271:1)
        at Object.eval [as updateDirectives] (ng:///DynamicTestModule/AlertComponent_Host.ngfactory.js:10:5)
        at Object.debugUpdateDirectives [as updateDirectives] (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45259:1)
        at checkAndUpdateView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:44271:1)
Chrome 80.0.3987 (Linux 0.0.0): Executed 16 of 23 (1 FAILED) (0 secs / 2.133 secs)
Chrome 80.0.3987 (Linux 0.0.0) AlertsComponent should be created FAILED
    TypeError: this.http.get is not a function
    error properties: Object({ ngDebugContext: DebugContext_({ view: Object({ def: Object({ factory: Function, nodeFlags: 33669121, rootNodeFlags: 33554433, nodeMatchedQueries: 0, flags: 0, nodes: [ Object({ nodeIndex: 0, parent: null, renderParent: null, bindingIndex: 0, outputIndex: 0, checkIndex: 0, flags: 33554433, childFlags: 114688, directChildFlags: 114688, childMatchedQueries: 0, matchedQueries: Object({  }), matchedQueryIds: 0, references: Object({  }), ngContentIndex: null, childCount: 1, bindings: [  ], bindingFlags: 0, outputs: [  ], element: Object({ ns: '', name: 'naas-alerts', attrs: [  ], template: null, componentProvider: Object({ nodeIndex: 1, parent: <circular reference: Object>, renderParent: <circular reference: Object>, bindingIndex: 0, outputIndex: 0, checkIndex: 1, flags: 114688, childFlags: 0, directChildFlags: 0, childMatchedQueries: 0, matchedQueries: Object, matchedQueryIds: 0, references: Object, ngContentIndex: -1, childCount: 0, bindings: Array, bindingFlags: 0, outputs: Array ...
        at <Jasmine>
        at AlertService.getAlerts (http://localhost:9876/_karma_webpack_/src/app/alerts.service.ts:20:22)
        at AlertComponent.ngOnInit (http://localhost:9876/_karma_webpack_/src/app/alerts/alerts.component.ts:18:23)
        at checkAndUpdateDirectiveInline (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:31910:1)
        at checkAndUpdateNodeInline (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:44367:1)
        at checkAndUpdateNode (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:44306:1)
        at debugCheckAndUpdateNode (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45328:36)
        at debugCheckDirectivesFn (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45271:1)
        at Object.eval [as updateDirectives] (ng:///DynamicTestModule/AlertComponent_Host.ngfactory.js:10:5)
        at Object.debugUpdateDirectives [as updateDirectives] (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/fesm2015/core.js:45259:1)
Chrome 80.0.3987 (Linux 0.0.0): Executed 23 of 23 (1 FAILED) (2.504 secs / 2.346 secs)

... I've been round & round T'Internet, and found lots on writing interceptors... but less on testing them. ...我一直在 T'Internet 上四处走动,发现了很多关于编写拦截器的文章......但在测试它们方面却很少。

I've spend far too long on this, and could do with advice.我在这方面花了太长时间,可以提供建议。

  1. Is this actually a valid testing solution (I looked at marbles but found that even less comprehensible)这实际上是一个有效的测试解决方案吗(我查看了marbles但发现它更难以理解)
  2. How can I make it work?我怎样才能让它工作?

This is what worked for me...这对我有用...

I specifically wanted to test the html the component produced, for a given response from the external API call - ie, I want to test the code in alerts.component.ts as much as possible我特别想测试组件生成的 html,对于来自外部 API 调用的给定响应 - 即,我想尽可能多地测试alerts.component.ts的代码

// alerts.component.spce.ts
import { By } from '@angular/platform-browser';
import {
  HttpClientTestingModule,
  HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Type } from '@angular/core';

import { AlertComponent } from './alerts.component';
import { AlertService } from '../alerts.service';

/*

This tests alerts.component.ts.

AlertComponent has an ngOnInit method, which uses AlertService.getAlerts

AlertService.getAlerts calls `/api/alerts/`
.... HOWEVER the HttpTestingController catches it & flushes back our canned
reponse.

This response is processed by AlertService.getAlerts & returned to
AlertComponent - which builds the fragment of html defined in
alerts.components.html - which we can test.

*/

describe('AlertsComponent', () => {
  let fixture: ComponentFixture<AlertComponent>;
  let httpMock: HttpTestingController;
  let alertComponent: AlertComponent;

  beforeEach( async () => {

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      declarations: [ AlertComponent ],
      providers: [ AlertService ],
    });

    await TestBed.compileComponents();

    fixture = TestBed.createComponent(AlertComponent);
    alertComponent = fixture.componentInstance;
    httpMock = fixture.debugElement.injector
      .get<HttpTestingController>(HttpTestingController as Type<HttpTestingController>);

    fixture.detectChanges();

  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    alertComponent.ngOnInit();
    const reqs = httpMock.match(`/api/alerts/`);
    for (const req of reqs) {
      req.flush(null);
    }
    fixture.detectChanges();
    expect(alertComponent).toBeTruthy();
  });

  it('The component should init, call the alert service, and get a response', () => {
    const dummyAlerts = [{
      id: 1,
      display_message: 'Foo',
      is_enabled: true,
    }];

    alertComponent.ngOnInit();
    const reqs = httpMock.match(`/api/alerts/`);
    for (const req of reqs) {
      req.flush(dummyAlerts);
    }
    fixture.detectChanges();
    const compiled = fixture.debugElement.queryAll(By.css('p'));
    expect(compiled.length).toBe(1);
  });

  it('The component should build 2 alerts from a response', () => {
    const dummyAlerts = [{
      id: 1,
      display_message: 'Foo',
      is_enabled: true,
    }, {
      id: 2,
      display_message: 'Bar',
      is_enabled: true,
    }];

    alertComponent.ngOnInit();
    const reqs = httpMock.match(`/api/alerts/`);
    for (const req of reqs) {
      req.flush(dummyAlerts);
    }
    fixture.detectChanges();
    const compiled = fixture.debugElement.queryAll(By.css('p'));
    expect(compiled.length).toBe(2);
  });

  // ## This fails when looking for 'small' - needs investigated ##
  it('The component should build 2 alerts from a response', () => {
    const dummyAlerts = [{
      id: 1,
      display_message: '<small>Foo</small>',
      is_enabled: true,
    }];

    alertComponent.ngOnInit();
    const reqs = httpMock.match(`/api/alerts/`);
    for (const req of reqs) {
      req.flush(dummyAlerts);
    }
    fixture.detectChanges();
    const compiled = fixture.debugElement.queryAll(By.css('p'));
    expect(compiled.length).toBe(1);
  });
});

(I'd be delighted to be told better ways of doing this) (我很高兴被告知更好的方法)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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