Angular 2错误:在Karma-Jasmine测试中没有Http的提供者

时间:2016-10-12 16:20:16

标签: angular karma-jasmine

即使我的应用程序完美运行且没有错误,我仍然在我的业力测试中收到以下错误。据说没有Http的提供者。我在app.module.ts文件中使用function mapDispatchToProps(dispatch) { return { foo: function() { dispatch(someAction()); return function asyncAction(dispatch, getState) { console.log("An async action!"); dispatch(someOtherAction(getState().foo)); }; } } } 并将其添加到imports数组中。业力错误如下所示:

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

这是我的app.component.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)

这是我的简单规范:

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); } } 语句,测试通过正常,但随后应用程序在浏览器中失败。我已就此做了大量研究,但未能找到解决方案。任何帮助将不胜感激!!

5 个答案:

答案 0 :(得分:24)

TestBed的目的是为测试环境从头开始配置@NgModule 。所以你目前所配置的只是AppComponent,而没有其他(除了已经在@Component.providers中声明的服务。

我强烈建议你这样做,而不是试图像在真实环境中那样配置一切,只是模仿BudgetService。尝试配置Http并模拟它并不是最好的主意,因为您希望在单元测试时尽可能保持外部依赖关系。

这是你需要做的事情

  1. BudgetService创建模拟。我会查看this post。您可以扩展该抽象类,添加getBudgets方法

  2. 您需要覆盖@Component.providers

  3. 中提及的Http

    如果您真的想要使用真实服务和MockBackend,那么您需要准备好模拟{"Message":"Authorization has been denied for this request."} 上的连接。您无法使用真实的后端,因为它依赖于平台浏览器。例如,请查看this post。在测试组件时,我个人认为这不是一个好主意。在测试您的服务时,这是 的时候。

答案 1 :(得分:13)

警告:此解决方案仅在您要测试静态结构时才有效。如果您的测试实际上进行了服务调用(并且您最好也进行了一些测试),它将无法工作。

您的测试使用自己的模块定义,测试模块,而不是您的AppModule。所以你必须在那里导入HttpModule:

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

您还可以导入AppModule:

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

这样做的好处是您不必在许多地方添加新的组件和模块。它更方便。另一方面,这不太灵活。您可能在测试中导入的数量超出了您的预期。

此外,您还有从低级组件到整个AppModule的依赖关系。事实上,这是一种循环依赖,通常是一个坏主意。所以在我看来,你应该只为那些对你的应用程序至关重要的高级组件这样做。对于可以重复使用的更多低级组件,最好在测试规范中明确列出所有依赖项。

答案 2 :(得分:4)

On Angular 4 +

RC2C's answer为我工作:)谢谢!

小心:仅当您真正调用您的服务时,此功能才有效。它仅在您想要测试静态结构时才有效。

只是想为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();
  }));

}

警告:请参阅顶部警告

答案 3 :(得分:3)

在app.module.ts中导入HttpModule,它将解决您的问题。

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

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

答案 4 :(得分:0)

peeskillet's answer中描述的模拟服务的替代方法是使用Mock Backend provided by 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();
     }));
});