Тестирование Angular 8 сервисов с помощью `TestHttpInterceptor`? - PullRequest
0 голосов
/ 05 февраля 2020

У меня довольно маленький Angular интерфейс, который получает большую часть своих данных с внешнего сервера.

Тесты дают мой gyp

Я могу протестировать простой компонент с помощью насмешливого сервиса, однако это общее решение "заменить вывод", а не реальный тест ... для этого я считаю, что мне нужно предоставить известный результат, когда служба вызывает внешний API.

Вот простой пример:

Определение объекта / интерфейса:

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

Определение службы:

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

Код компонента:

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

и

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

Затем я написал класс 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
            }));
        }
    }
  }
}

... и мой тест (да, у меня все еще есть некоторые старые _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);
  // });
});

Проблема в том, что когда я запускаю это, я получаю следующую ошибку:

    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)

... Я был кругом T'Inte rnet, и нашел много написания перехватчиков ... но меньше на их тестирование.

Я потратил слишком долго на это и мог бы советоваться.

  1. Является ли это действительно действующим решением для тестирования ( Я посмотрел на marbles, но обнаружил, что это еще менее понятно)
  2. Как я могу заставить его работать?

1 Ответ

0 голосов
/ 07 февраля 2020

Это то, что сработало для меня ...

Я специально хотел проверить html созданный компонент, для данного ответа от внешнего вызова API - ie, я хочу протестировать код в 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);
  });
});

(я был бы рад узнать, как лучше это сделать)

...