web-dev-qa-db-fra.com

Simulation de service personnalisé dans angular2 pendant le test unitaire

J'essaie d'écrire un test unitaire pour le composant utilisé dans mon service . Le composant et le service fonctionnent correctement.

Composant:

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

Un service:

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

Test unitaire composant:

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

Une partie de 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",
    ...
  }
}

Quand j'exécute 'karma start', j'obtiens cette erreur

Erreur: Erreur dans la classe ./PoniesComponent PoniesComponent_Host - modèle en ligne: 0: 0 provoqué par: Aucun fournisseur pour Http! dans config/karma-test-shim.js

Il semble que le karma utilise PonyService au lieu de se moquer de lui comme MockPonyService, malgré cette ligne: providers: [{provide: PonyService, useClass: MockPonyService}].

La question: comment je devrais me moquer du service?

15
Evgeniy

C'est à cause de ça

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

Ainsi, le service est limité au composant, ce qui signifie qu’ Angular le créera pour chaque composant et remplace tout fournisseur global configuré au niveau du module. Cela inclut le fournisseur factice que vous avez configuré dans le banc d’essai.

Pour résoudre ce problème, Angular fournit la méthode TestBed.overrideComponent, qui nous permet de remplacer des éléments tels que @Component.providers et @Component.template.

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

Une autre approche valable consiste à utiliser des jetons et à faire appel à Intefaces au lieu de classes de base ou de classes concrètes, ce que les dinosaures comme moi adorent faire ( DIP , DI et d’autres SOLID Blablahs). Et permettez à votre composant d’avoir ses dépendances injectées au lieu de le fournir vous-même dans votre propre composant.

Votre composant n’aurait aucun fournisseur, il recevrait l’objet en tant qu’interface dans son constructeur pendant la magie injection de dépendance. Voir @inject utilisé dans le constructeur et voir la valeur "fourniture" dans les fournisseurs en tant que texte plutôt qu'en tant que classe.

Ainsi, votre composant changerait pour quelque chose comme:

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

Dans votre pièce @Component, vous devez supprimer le fournisseur et l'ajouter à un composant parent tel que "app.component.ts". Là, vous ajouteriez un jeton:

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

Votre composant de test unitaire (l’analogue de app.component.ts) aurait: fournisseurs: [{fournir: 'PonyServiceInterface', useClass: MockPonyService}]

Donc, votre composant ne se soucie pas de ce que fait le service, il utilise simplement l'interface, injectée via le composant parent (app.component.ts ou votre composant de test unitaire).

FYI: L’approche @inject n’est pas très répandue et, à un moment donné, il semble que les boursiers angulaires préfèrent les classes de base aux interfaces en raison du fonctionnement du javascript sous-jacent.

1
Adrián Poplavsky