【问题标题】:Angular 2 component testing with Jasmine spies "No provider for Http!" error使用 Jasmine 间谍进行 Angular 2 组件测试“没有 Http 提供者!”错误
【发布时间】:2017-03-03 10:59:17
【问题描述】:

我正在尝试测试使用服务的 Angular 2 组件。该服务已注入 Http,我对测试不感兴趣,因此我试图模拟该服务并监视该服务的方法调用。这是我在 Angular 1 中非常熟悉的事情,但我无法在 Angular 2 中工作。我得到的错误是 No provider for Http!我有兴趣监视实际的服务方法,而不是嘲笑它。

我的组件如下所示:

import { Component, OnInit } from '@angular/core';
import { NavBarLink } from '../../models/nav-bar-link';
import { NavBarService } from '../../services/nav-bar/nav-bar.service';

@Component({
    selector: 'nav-bar',
    providers: [NavBarService],
    moduleId: module.id,
    templateUrl: 'nav-bar.template.html'
})
export class NavBarComponent {
  constructor(private _navBarService: NavBarService) { }

 links: NavBarLink[];

 getLinks(): void {
  this._navBarService.getNavBarLinks().then(data => this.links = data);
 }

ngOnInit(): void {
   this.getLinks();
  }
}

我的服务看起来像这样:

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Urls } from '../../constants/urls.constants';
import { NavBarLink } from '../../models/nav-bar-link';

@Injectable()
export class NavBarService {
    constructor(private _http: Http,
                private _urls: Urls) { }

    getNavBarLinks():Promise<NavBarLink[]> {

        return this._http.get(this._urls.NAV_BAR_LINKS)    
            .toPromise()
            .then(response => {
                 let navLinks = [];
                 for(let navLink of response.json()) {
                    navLinks.push(new NavBarLink(navLink.id,        navLink.description, navLink.route));
                }
                return navLinks;
            })
            .catch(this.handleError);

    }

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

}

最后我的测试看起来像这样

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Provider } from '@angular/core';
import { By }              from '@angular/platform-browser';
import { DebugElement }    from '@angular/core';

import { NavBarComponent } from './nav-bar.component';
import { NavBarService } from '../../services/nav-bar/nav-bar.service';

import { Observable } from 'rxjs/Rx';

let comp: NavBarComponent;
let fixture: ComponentFixture<NavBarComponent>;
let navBarService;

class MockNavBarService extends NavBarService{
    constructor() {
        super(null, null);
     }
}

describe ('NavBarComponent tests', () => {

    beforeEach( async(() => {
        TestBed.configureTestingModule({
                declarations: [ NavBarComponent ],
                providers: [ {provide: NavBarService, useClass:     MockNavBarService} ]
            })
            .compileComponents()
            .then(createComponent);
    }));


    it('should call the getNavBarLinks when ngOnInit is called', () => {
        comp.ngOnInit();
        expect(navBarService.getNavBarLinks).toHaveBeenCalled();
    });
});

function createComponent() {
    fixture = TestBed.createComponent(NavBarComponent);
    comp = fixture.componentInstance;

    navBarService = fixture.debugElement.injector.get(NavBarService);

    spyOn(navBarService,    'getNavBarLinks').and.returnValue(Promise.resolve([]));
}

【问题讨论】:

    标签: unit-testing angular jasmine components


    【解决方案1】:

    感谢大家的帮助,这让我找到了正确的区域。缺少的是导入 HttpModule。非常感谢艾哈迈德提出这个建议。这是我的固定测试供参考:

    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    import { Provider } from '@angular/core';
    import { By }              from '@angular/platform-browser';
    import { DebugElement }    from '@angular/core';
    import { HttpModule } from '@angular/http'; // <==== IMPORT THIS
    
    import { MockBackend } from '@angular/http/testing';
    import { NavBarComponent } from './nav-bar.component';
    import { NavBarService } from '../../services/nav-bar/nav-bar.service';
    import { Urls } from '../../constants/urls.constants';
    
    import { Observable } from 'rxjs/Rx';
    
    let comp: NavBarComponent;
    let fixture: ComponentFixture<NavBarComponent>;
    var navBarService;
    
    describe ('NavBarComponent tests', () => {
    
        beforeEach( async(() => {
            TestBed.configureTestingModule({
                    declarations: [ NavBarComponent ],
                    imports: [ HttpModule], //<==IMPORT INTO TEST BED
                    providers: [
                        Urls,
                        MockBackend,
                        NavBarService ]
                })
               .compileComponents()
               .then(createComponent);
        }));
    
    
        it('should call the getNavBarLinks when ngOnInit is called', () => {
            comp.ngOnInit();
            expect(navBarService.getNavBarLinks).toHaveBeenCalled();
        });
    });
    
    function createComponent() {
        fixture = TestBed.createComponent(NavBarComponent);
        comp = fixture.componentInstance;
    
        navBarService = fixture.debugElement.injector.get(NavBarService);
    
        spyOn(navBarService,     'getNavBarLinks').and.returnValue(Promise.resolve([]));
    }
    

    不需要模拟对象或任何东西,完美

    【讨论】:

    • 此解决方案的问题在于,您引入的模块是组件所使用的服务的依赖项,这增加了组件测试的复杂性。每当 NavBarService 更改其提供程序时,您都必须更新使用或依赖于 NavBarComponent 的每个测试文件。通过使用不扩展原始服务的类来模拟服务,您可以从根本上消除依赖复杂性。如果您更改服务,这将使您的测试更清晰,并且不易引起注意。
    【解决方案2】:

    因为这个

    @Component({
        providers: [NavBarService],   <==== !!!!!
    })
    export class NavBarComponent {
    

    @Component.providers 优先于提供的任何内容和模块级别。因此 Angular 将尝试创建 NavBarService 的实例,而不是使用您在测试中配置的模拟。

    Angular 允许我们覆盖@Component.providers,以及其他类似@Component.template 的东西。我们可以通过以下方式做到这一点

    TestBed.configureTestingModule({
      declarations: [NavBarComponent]
    })
    .overrideComponent(NavBarComponent, {
      set: {
        providers: [
          { provide: NavBarService, useClass: MockNavBarService}
        ]
      }
    })
    .compileComponents()
     .then(createComponent)
    

    【讨论】:

      【解决方案3】:

      问题是MockNavBarService扩展了NavBarService,所以还是期望提供Http的。我不明白为什么会出现这种情况的技术原因,但确实如此。如果您删除继承,您可以改为实现一个模拟getNavBarLinks(),它返回一些罐装数据的 Observable。然后在您的测试中,您可以测试 NavBarComponent 对数据的作用,而不是测试某个方法被调用的事实。

      【讨论】:

        猜你喜欢
        • 2017-02-21
        • 1970-01-01
        • 2017-12-22
        • 1970-01-01
        • 2015-07-26
        • 1970-01-01
        • 2015-09-29
        • 1970-01-01
        • 2017-01-22
        相关资源
        最近更新 更多