Publié le 08/07/2017, rédigé par
Chloé MAHALIN
Angular 4
Mocker un service injectable pour tester un composant
Souvent
- les composants angular se font injecter des services
- qui remplissent une fonction comme celle d'envoyer des requêtes auprès d'un serveur distant.
Pour tester un composant Angular qui dispose d'un service
- il y a deux solutions. Je vous recommande la seconde
- qui est beaucoup plus simple à lire et à gérer.
Définition des composants et services
Mocker un service ressemble énormément au mock d'un composant enfant :
app.component.ts
import { Component
- Output
- EventEmitter } from '@angular/core';
import { MyService } from '../services/service';
@Component({
selector: 'app-component'
-
templateUrl: './app.component.html'
-
styleUrls: ['./app.component.css']
-
providers: [ MyService ]
})
export class AppComponent {
private myService: MyService;
title: string = 'toto';
constructor(myService: MyService) {
this.myService = myService;
}
getTitle() {
myService.getTitleFromId(1).subscribe(
entity => {this.title = entity; }
-
error => console.log(<any>error)
);
}
}
app.component.html
<h1>{{ title }}</h1>
<button (click)="getTitle();">Validate</button>
service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class MyService {
private myUrl = 'api/machin'; // URL to web API
private http: Http;
constructor(http: Http) {
this.http = http;
}
getTitleFromId(id: number): Observable<string> {
const url = `${this.myUrl}/get/${id}`;
return this.http.get(url)
.map(res => super.extractSingleEntityFromResponse(res))
.catch(super.handleError);
}
//...
}
La classe service n'est complète
- elle est détaillée dans l'article explicatif de la rédaction d'un service effectuant des requetes HTTP.
Démonstration du test par mock du service
Maintenant que l'on a défini notre composant et notre service
- voici comment tester le composant en mockant le service :
import { ComponentFixture
- TestBed } from '@angular/core/testing';
import { DebugElement
- Component
- Injectable } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { AppComponent } from '../../../app/components/app.component';
import { MyService } from '../../../app/services/service';
// This is the mock of our service !
@Injectable
class MockService extends MyService {
constructor(http: Http) {}
getTitleFromId(id: number): Observable<string> { return Observable.of('mockedValue'); }
}
describe('Test Component Workflow :'
- () => {
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let myService: MyService;
beforeEach(() => {
TestBed.configureTestingModule({
imports : [ HttpModule ]
- // As your mock extends MyService
- it requires to inject http and
declarations: [ AppComponent ]
- // the associated module.
providers: [ { provide: MyService
- useClass: MockService } ] //<== You need to use useClass and not use Value.
});
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
// service actually injected into the component
myService = fixture.debugElement.injector.get(MyService);
});
it('click on validate button should retrieve the title through service'
- () => {
spyOn(myService
- 'getTitleFromId');
// trigger the click
const button = fixture.debugElement.query(By.css('button')).nativeElement;
button.dispatchEvent(new Event('click'));
expect(myService.getTitleFromId).toHaveBeenCalledWith(1);
});
});
Démonstration de test en épiant et court-circuitant le service
Voici comment tester le composant en interceptant les appels au service :
import { ComponentFixture
- TestBed } from '@angular/core/testing';
import { DebugElement
- Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { AppComponent } from '../../../app/components/app.component';
import { MyService } from '../../../app/services/service';
describe('Test Component Workflow :'
- () => {
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let myService: MyService;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent ]
-
providers: [ MyService ]
});
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
// service actually injected into the component
myService = fixture.debugElement.injector.get(MyService);
});
it('click on validate button should retrieve the title through service'
- () => {
spyOn(myService
- 'getTitleFromId');
// trigger the click
const button = fixture.debugElement.query(By.css('button')).nativeElement;
button.dispatchEvent(new Event('click'));
expect(myService.getTitleFromId).toHaveBeenCalledWith(1);
});
});
Le contenu est quasiment identique
- à la différence que l'on laisse Jasmine intercepter les appels au service avec les Spy. Attention toutefois
- si vous n'épiez pas votre service
- des requêtes partiront et ralentiront vos tests avec des erreurs 404.