web-dev-qa-db-fra.com

Erreur angulaire 2: aucun fournisseur pour Http dans le test Karma-Jasmine

Je continue à avoir l'erreur suivante dans mon test de karma même si mon application fonctionne parfaitement sans erreur. Il est dit qu'il n'y a pas de fournisseur pour Http. J'utilise import { HttpModule } from '@angular/http'; dans mon fichier app.module.ts et je l'ajoute au tableau imports. L'erreur de karma ressemble à ceci:

Chrome 52.0.2743 (Mac OS X 10.12.0) App: TrackBudget should create the app FAILED
    Failed: Error in ./AppComponent class AppComponent_Host - inline template:0:0 caused by: No provider for Http!
    Error: No provider for Http!
        at NoProviderError.Error (native)
        at NoProviderError.BaseError [as constructor] (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/facade/errors.js:24:0 <- src/test.ts:2559:34)
        at NoProviderError.AbstractProviderError [as constructor] (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_errors.js:42:0 <- src/test.ts:15415:16)
        at new NoProviderError (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_errors.js:73:0 <- src/test.ts:15446:16)
        at ReflectiveInjector_._throwOrNull (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_injector.js:761:0 <- src/test.ts:26066:19)
        at ReflectiveInjector_._getByKeyDefault (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_injector.js:789:0 <- src/test.ts:26094:25)
        at ReflectiveInjector_._getByKey (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_injector.js:752:0 <- src/test.ts:26057:25)
        at ReflectiveInjector_.get (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/src/di/reflective_injector.js:561:0 <- src/test.ts:25866:21)
        at TestBed.get (webpack:/Users/ChrisGaona%201/budget-tracking/~/@angular/core/bundles/core-testing.umd.js:1115:0 <- src/test.ts:5626:67)
Chrome 52.0.2743 (Mac OS X 10.12.0): Executed 1 of 1 (1 FAILED) ERROR (0.229 secs / 0.174 secs)

Voici mon fichier app.component.ts:

import {Component} from '@angular/core';
import {Budget} from "./budget";
import {BudgetService} from "./budget.service";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    providers: [BudgetService]
})
export class AppComponent {
    title = 'Budget Tracker';

    budgets: Budget[];
    selectedBudget: Budget;

    constructor(private budgetService: BudgetService) { }

    ngOnInit(): void {
        this.budgetService.getBudgets()
            .subscribe(data => {
                this.budgets = data;
                console.log(data);
                this.selectedBudget = data[0];
                console.log(data[0]);
            });
    }
}

Voici ma simple spécification: 

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('App: TrackBudget', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
        declarations: [
            AppComponent
        ]
    });
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
});

L'erreur semble être causée par mon service, qui peut être vu ici: 

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import {Budget} from "./budget";

@Injectable()
export class BudgetService {

  constructor(public http: Http) { }

  getBudgets() {
    return this.http.get('budget.json')
        .map(response => <Budget[]>response.json().budgetData);

  }
}

Si je supprime l'instruction constructor(public http: Http) { } du service, le test réussit, mais l'application échoue dans le navigateur. J'ai fait beaucoup de recherches à ce sujet et je n'ai pas été en mesure de trouver la solution. Toute aide serait grandement appréciée !!

17
Chris

La TestBed a pour but de configurer un @NgModuleà partir de zéro pour l'environnement de test. Donc, actuellement, tout ce que vous avez configuré est la AppComponent et rien sinon (sauf le service déjà déclaré dans le @Component.providers.

Au lieu d'essayer de tout configurer comme dans un environnement réel, je vous suggère fortement de vous moquer de la variable BudgetService. Essayer de configurer la variable Http et de la simuler n'est pas la meilleure idée, car vous souhaitez conserver les dépendances externes aussi claires que possible lors des tests unitaires.

Voici ce que vous devez faire

  1. Créez une maquette pour la BudgetService. Je voudrais vérifier ce post . Vous pouvez simplement étendre cette classe abstraite en ajoutant votre méthode getBudgets

  2. Vous devez remplacer le @Component.providers, comme indiqué dans this post

Si vous voulez vraiment utiliser uniquement le service réel et la Http, vous devez être prêt à simuler des connexions sur la MockBackend. Vous ne pouvez pas utiliser le véritable backend, car il dépend du navigateur de la plate-forme. Pour un exemple, consultez cet article . Personnellement, je ne pense pas que ce soit une bonne idée lors du test des composants. Lorsque vous testez votre service, c’est à ce moment-là que vous devriez le faire.

23
Paul Samsotha

Attention : Cette solution ne fonctionne que si vous souhaitez tester la structure statique. Cela ne fonctionnera pas si votre test passe réellement des appels de service (et il vaut mieux avoir certains de ces tests).

Votre test utilise une définition de module propre, un module de test, et non votre AppModule. Vous devez donc importer HttpModule ici aussi:

TestBed.configureTestingModule({
    imports: [
        HttpModule
    ],
    declarations: [
        AppComponent
    ]
});

Vous pouvez également importer votre AppModule:

TestBed.configureTestingModule({
    imports: [
        AppModule
    ]
});

Cela présente l'avantage que vous n'avez pas besoin d'ajouter de nouveaux composants et modules à de nombreux endroits. C'est plus pratique. D'autre part, c'est moins flexible. Vous importez peut-être plus que vous ne le souhaiteriez dans votre test. 

De plus, vous dépendez de votre composant de bas niveau pour l'ensemble de l'AppModule. En fait, c'est une sorte de dépendance circulaire qui est normalement une mauvaise idée. Donc, à mes yeux, vous ne devriez le faire que pour les composants de haut niveau qui sont de toute façon au cœur de votre application. Pour plus de composants de bas niveau pouvant même être réutilisés, il vaut mieux lister toutes les dépendances explicitement dans la spécification de test.

12
R2C2

Sur angulaire 4+

La réponse de RC2C a fonctionné pour moi :) Merci!

Attention: Ceci ne fonctionnera que si pas n'appelle vraiment votre service. Cela ne fonctionne que si vous voulez tester la structure statique.

Je voulais juste ajouter que pour la version angulaire 4 (et plus, probablement), vous devriez importer HttpClientModule sur votre banc d’essai, afin qu’il ressemble à ceci:

import { HttpClientModule } from '@angular/common/http';


describe('BuildingService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
      providers: [BuildingService]
    });
  });

  it('should be created 2', inject([BuildingService], (service: BuildingService) => {
    expect(service).toBeTruthy();
  }));

}

Caution: Voir haut Caution

4
Filip Savic

Importez HttpModule dans app.module.ts et cela résoudra votre problème.

import { HttpModule } from '@angular/http';

@NgModule({
    imports: [HttpModule]
})
...
3
SUBHASIS MONDAL

Une solution de rechange au service moqueur décrite dans La réponse de peeskillet consiste à utiliser le Mock Backend fourni par angular .

La documentation de l'API contient l'exemple suivant:

import {Injectable, ReflectiveInjector} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
import {Response, ResponseOptions} from '@angular/http';
import {MockBackend, MockConnection} from '@angular/http/testing';

const HERO_ONE = 'HeroNrOne';
const HERO_TWO = 'WillBeAlwaysTheSecond';

@Injectable()
class HeroService {
  constructor(private http: Http) {}

  getHeroes(): Promise<String[]> {
    return this.http.get('myservices.de/api/heroes')
        .toPromise()
        .then(response => response.json().data)
        .catch(e => this.handleError(e));
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

describe('MockBackend HeroService Example', () => {
  beforeEach(() => {
    this.injector = ReflectiveInjector.resolveAndCreate([
      {provide: ConnectionBackend, useClass: MockBackend},
      {provide: RequestOptions, useClass: BaseRequestOptions},
      Http,
      HeroService,
    ]);
    this.heroService = this.injector.get(HeroService);
    this.backend = this.injector.get(ConnectionBackend) as MockBackend;
    this.backend.connections.subscribe((connection: any) => this.lastConnection = connection);
  });

  it('getHeroes() should query current service url', () => {
    this.heroService.getHeroes();
    expect(this.lastConnection).toBeDefined('no http service connection at all?');
    expect(this.lastConnection.request.url).toMatch(/api\/heroes$/, 'url invalid');
  });

  it('getHeroes() should return some heroes', fakeAsync(() => {
       let result: String[];
       this.heroService.getHeroes().then((heroes: String[]) => result = heroes);
       this.lastConnection.mockRespond(new Response(new ResponseOptions({
         body: JSON.stringify({data: [HERO_ONE, HERO_TWO]}),
       })));
       tick();
       expect(result.length).toEqual(2, 'should contain given amount of heroes');
       expect(result[0]).toEqual(HERO_ONE, ' HERO_ONE should be the first hero');
       expect(result[1]).toEqual(HERO_TWO, ' HERO_TWO should be the second hero');
     }));

  it('getHeroes() while server is down', fakeAsync(() => {
       let result: String[];
       let catchedError: any;
       this.heroService.getHeroes()
           .then((heroes: String[]) => result = heroes)
           .catch((error: any) => catchedError = error);
       this.lastConnection.mockRespond(new Response(new ResponseOptions({
         status: 404,
         statusText: 'URL not Found',
       })));
       tick();
       expect(result).toBeUndefined();
       expect(catchedError).toBeDefined();
     }));
});
0
schnatterer