【问题标题】:Angular 2 Error: No provider for Http in Karma-Jasmine TestAngular 2 错误:Karma-Jasmine 测试中没有 Http 提供者
【发布时间】:2017-02-21 13:10:40
【问题描述】:

即使我的应用程序运行良好且没有错误,我仍然在我的业力测试中收到以下错误。就是说没有 Http 的提供者。我在 app.module.ts 文件中使用 import { HttpModule } from '@angular/http'; 并将其添加到导入数组中。业力错误如下所示:

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) { } 语句,则测试可以正常通过,但是应用程序在浏览器中失败。我对此进行了很多研究,但无法找出解决方案。任何帮助将不胜感激!

【问题讨论】:

    标签: angular karma-jasmine


    【解决方案1】:

    注意:此解决方案仅在您想测试静态结构时才有效。如果您的测试确实进行了服务调用(并且您最好也进行其中一些测试),那么它将无法正常工作。

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

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

    您也可以导入您的 AppModule:

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

    这样做的好处是您不必在很多地方添加新的组件和模块。它更方便。另一方面,这不太灵活。您可能会在测试中导入比您希望的更多的内容。

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

    【讨论】:

    • 这行不通。你需要use the mock backend
    • @peeskillet 你是对的。这仅适用于实际上不调用后端而仅测试静态结构的测试。感谢您指出这一点。
    【解决方案2】:

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

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

    这就是你需要做的事情

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

    2. 您需要覆盖@Component.providers,如this post中所述

    如果你真的只想使用真正的服务和Http,那么你需要准备好在MockBackend 上模拟连接。您不能使用真正的后端,因为它依赖于平台浏览器。例如,查看this post。我个人认为在测试组件时这不是一个好主意。在测试您的服务时,这是您应该这样做的时候。

    【讨论】:

    • 感谢您的帮助!它似乎有效,但我现在收到此警告:Critical dependency: the request of a dependency is an expression。这是正常的还是我做错了什么?
    • 不确定,因为我看不到你做了什么。虽然不正常
    • 很抱歉。我真的很感谢你的帮助!现在,在 Angular 2 中进行测试对我来说是一种全新的体验。您可以在我制作的 plunker here 中看到我更改的文件。
    • 只是快速查看它,您从未设置过数据。查看第一点链接中的示例。您将在底部看到一个示例,其中在模拟上设置了 content。您还需要将useValue 与模拟的instance 一起使用,而不是useClass 与模拟的类一起使用。
    • 我认为这不会导致您面临的错误。我刚刚测试了你的代码,它运行良好(没有错误)。
    【解决方案3】:

    在 app.module.ts 中导入 HttpModule 即可解决您的问题。

    import { HttpModule } from '@angular/http';
    
    @NgModule({
        imports: [HttpModule]
    })
    ...
    

    【讨论】:

    • 欢迎来到 SO。请阅读此how-to-answer 以提供高质量的答案。
    • 可以肯定的是,任何通过服务进行 api 调用的应用程序都已经安装了该模块。
    【解决方案4】:

    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();
         }));
    });
    

    【讨论】:

    • 喜欢复制和粘贴答案。
    • @wordpress_designer 这个解决方案最适合我。由于我在这里的答案中没有找到它,所以我分享了它。如果您想改进它,请使用编辑按钮。如果您认为它不合适,请标记它。停止拖钓!
    【解决方案5】:

    在 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();
      }));
    
    }
    

    注意:见顶部注意

    【讨论】:

    • 其他不需要 完全 OP 要求的人会提出这个问题(就像我一样)并阅读回复。这可能对他们中的一些人有所帮助。
    猜你喜欢
    • 2017-12-22
    • 1970-01-01
    • 1970-01-01
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-26
    • 1970-01-01
    相关资源
    最近更新 更多