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.






