web-dev-qa-db-de.com

Mock-Custom-Service in angle2 während des Gerätetests

Ich versuche, einen Komponententest für eine Komponente zu schreiben, die in meinem Service verwendet wird. Komponenten und Service funktionieren einwandfrei.

Komponente:

import {Component} from '@angular/core';
import {PonyService} from '../../services';
import {Pony} from "../../models/pony.model";
@Component({
  selector: 'el-ponies',
  templateUrl: 'ponies.component.html',
  providers: [PonyService]
})
export class PoniesComponent {
  ponies: Array<Pony>;
  constructor(private ponyService: PonyService) {
    this.ponies = this.ponyService.getPonies(2);
  }
  refreshPonies() {
    this.ponies = this.ponyService.getPonies(3);
  }
}

Bedienung:

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import {Pony} from "../../models/pony.model";
@Injectable()
export class PonyService {
  constructor(private http: Http) {}
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    this.http.get('http://localhost:8080/js-backend/ponies')
    .subscribe(response => {
      response.json().forEach((tmp: Pony)=> { toReturn.Push(tmp); });
      if (count && count % 2 === 0) { toReturn.splice(0, count); } 
      else { toReturn.splice(count); }
    });
    return toReturn;
  }}

Komponententest:

import {TestBed} from "@angular/core/testing";
import {PoniesComponent} from "./ponies.component";
import {PonyComponent} from "../pony/pony.component";
import {PonyService} from "../../services";
import {Pony} from "../../models/pony.model";
describe('Ponies component test', () => {
  let poniesComponent: PoniesComponent;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [PoniesComponent, PonyComponent],
      providers: [{provide: PonyService, useClass: MockPonyService}]
    });
    poniesComponent = TestBed.createComponent(PoniesComponent).componentInstance;
  });
  it('should instantiate component', () => {
    expect(poniesComponent instanceof PoniesComponent).toBe(true, 'should create PoniesComponent');
  });
});

class MockPonyService {
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    if (count === 2) {
      toReturn.Push(new Pony('Rainbow Dash', 'green'));
      toReturn.Push(new Pony('Pinkie Pie', 'orange'));
    }
    if (count === 3) {
      toReturn.Push(new Pony('Fluttershy', 'blue'));
      toReturn.Push(new Pony('Rarity', 'purple'));
      toReturn.Push(new Pony('Applejack', 'yellow'));
    }
    return toReturn;
  };
}

Teil von package.json:

{
  ...
  "dependencies": {
    "@angular/core": "2.0.0",
    "@angular/http": "2.0.0",
    ...
  },
  "devDependencies": {
    "jasmine-core": "2.4.1",
    "karma": "1.2.0",
    "karma-jasmine": "1.0.2",
    "karma-phantomjs-launcher": "1.0.2",
    "phantomjs-prebuilt": "2.1.7",
    ...
  }
}

Wenn ich 'Karma Start' ausführe, erhalte ich diesen Fehler

Fehler: Fehler in der ./PoniesComponent-Klasse PoniesComponent_Host - Inline-Vorlage: 0: 0, verursacht durch: Kein Anbieter für HTTP! in config/karma-test-shim.js

Es scheint, als würde Karma PonyService verwenden, anstatt es als MockPonyService zu verspotten, trotz dieser Zeile: providers: [{provide: PonyService, useClass: MockPonyService}].

Die Frage: Wie soll ich den Service verspotten?

15
Evgeniy

Es ist deshalb so

@Component({
  providers: [PonyService]  <======
})

Dadurch wird der Service auf die Komponente beschränkt, was bedeutet, dass Angular ihn für jede Komponente erstellt, und dass er alle auf Modulebene konfigurierten globalen Anbieter ersetzt. Dazu gehört der Modellanbieter, den Sie im Testbett konfigurieren.

Um dies zu umgehen, bietet Angular die TestBed.overrideComponent-Methode, mit der Dinge wie @Component.providers und @Component.template überschrieben werden können.

TestBed.configureTestingModule({
  declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
  set: {
    providers: [
      {provide: PonyService, useClass: MockPonyService}
    ]
  }
});
20
Paul Samsotha

Ein weiterer gültiger Ansatz ist die Verwendung von Token und die Verwendung von Intefaces anstelle von Basisklassen oder konkreten Klassen, die Dinosaurier wie ich gerne tun ( DIP , DI und andere SOLID Blablahs). Und lassen Sie zu, dass die Abhängigkeiten Ihrer Komponente eingefügt werden, anstatt sie selbst in Ihrer eigenen Komponente bereitzustellen.

Ihre Komponente hätte keinen Provider, sie würde das Objekt während des Winkels in ihrem Konstruktor als Schnittstelle erhalten zauber Abhängigkeitsspritze. Siehe @inject, das im Konstruktor verwendet wird, und den Wert "Bereitstellen" in Providern als Text und nicht als Klasse.

Ihre Komponente würde sich also folgendermaßen ändern:

constructor(@Inject('PonyServiceInterface') private ponyService: IPonyService) {
   this.ponies = this.ponyService.getPonies(2); }

In Ihrem @Component-Teil würden Sie den Anbieter entfernen und ihn einer übergeordneten Komponente wie "app.component.ts" hinzufügen. Dort würden Sie ein Token hinzufügen:

providers: [{provide: 'PonyServiceInterface', useClass: PonyService}]

Ihre Komponententestkomponente (das Analog zu app.component.ts) hätte: provider: [{bieten: 'PonyServiceInterface', useClass: MockPonyService}]

Ihre Komponente kümmert sich also nicht darum, was der Dienst tut, sie verwendet lediglich die über die übergeordnete Komponente (app.component.ts oder Ihre Komponententestkomponente) eingesetzte Schnittstelle.

Zu Ihrer Information: Der @ inject-Ansatz ist nicht sehr verbreitet, und irgendwann sieht es so aus, als würden eckige Kerle Baseclasses gegenüber Schnittstellen bevorzugen, da das zugrunde liegende Javascript funktioniert.

1