Angular 2 Ошибка: нет провайдера Http в тестах Karma-Jasmine
Я продолжаю получать следующую ошибку в своем тестировании кармы, даже несмотря на то, что мое приложение работает отлично, без ошибок. Он говорит, что нет провайдера Http. Я использую import { HttpModule } from '@angular/http';
в моем файле app.module.ts и добавляю его в массив импорта. Ошибка кармы выглядит следующим образом:
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)
Вот мой файл 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]);
});
}
}
Вот моя простая спецификация:
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();
}));
});
Ошибка, по-видимому, вызвана моей службой, которую можно увидеть здесь:
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);
}
}
Если я удалю оператор constructor(public http: Http) { }
из службы, тест проходит нормально, но затем приложение не работает в браузере. Я провел довольно много исследований по этому вопросу и не смог найти решение. Любая помощь будет принята с благодарностью!
Ответы
Ответ 1
Цель TestBed
заключается в настройке @NgModule
с нуля для тестовой среды. Таким образом, в настоящее время все, что вы настроили, это AppComponent
, и ничего больше (кроме службы, уже объявленной в @Component.providers
.
То, что я настоятельно рекомендую вам сделать, вместо того, чтобы пытаться настроить все, как вы, в реальной среде, просто издеваться над BudgetService
. Попытка настроить Http
и высмеять это не самая лучшая идея, так как вы хотите, чтобы внешние измерения были максимально легкими при тестировании модулей.
Вот что вам нужно сделать
-
Создайте макет для BudgetService
. Я бы посмотрел этот пост. Вы можете просто расширить этот абстрактный класс, добавив ваш метод getBudgets
-
Вам нужно переопределить @Component.providers
, как указано в этом сообщении
Если вы действительно хотите использовать реальный сервис и Http
, тогда вам нужно быть готовым к подключению соединений на MockBackend
. Вы не можете использовать реальный бэкэнд, так как он зависит от браузера платформы. Например, просмотрите этот пост. Я лично не думаю, что это хорошая идея, хотя при тестировании компонентов. При тестировании вашего сервиса это происходит, когда вы должны это делать.
Ответ 2
Внимание. Это решение работает только в том случае, если вы хотите протестировать статическую структуру. Он не будет работать, если ваш тест действительно вызывает служебные вызовы (и у вас также есть некоторые из этих тестов).
В вашем тесте используется собственное определение модуля, модуль тестирования, а не ваш AppModule. Поэтому вам также нужно импортировать HttpModule:
TestBed.configureTestingModule({
imports: [
HttpModule
],
declarations: [
AppComponent
]
});
Вы также можете импортировать свой AppModule:
TestBed.configureTestingModule({
imports: [
AppModule
]
});
Это имеет то преимущество, что вам не нужно добавлять новые компоненты и модули во многих местах. Это более удобно. С другой стороны, это менее гибко. Вы можете импортировать больше, чем вы хотели бы в своем тесте.
Кроме того, у вас есть зависимость от вашего низкоуровневого компонента для всего AppModule. На самом деле такая циклическая зависимость, которая обычно плохая идея. Поэтому в моих глазах вы должны делать это только для компонентов высокого уровня, которые в любом случае являются очень важными для вашего приложения. Для более низкоуровневых компонентов, которые могут быть даже многократно использованы, вы лучше перечисляете все зависимости явно в спецификации теста.
Ответ 3
Импортируйте HttpModule в app.module.ts, и он решит вашу проблему.
import { HttpModule } from '@angular/http';
@NgModule({
imports: [HttpModule]
})
...
Ответ 4
Альтернатива издевательству службы, описанной в ответе peeskillet, использует Mock Backend предоставлено angular.
В документе API содержится следующий пример:
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();
}));
});
Ответ 5
Вкл Angular 4 +
Ответ RC2C работал у меня:) Спасибо!
Предостережение. Это будет работать, только если вы не действительно звоните в службу. Он работает только в том случае, если вы хотите протестировать статическую структуру.
Просто хотел добавить, что для Angular версии 4 (и выше, вероятно) вы должны импортировать HttpClientModule
на тестовый стенд, чтобы он выглядел следующим образом:
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();
}));
}
Осторожно: см. верхний Предостережение