使用Jasmine Spies进行Angular 2组件测试“没有Http提供商!”错误

时间:2016-10-21 08:08:24

标签: unit-testing angular jasmine components

我正在尝试测试使用服务的Angular 2组件。该服务注入了Http,我对测试不感兴趣所以我试图模拟服务并监视服务的方法调用。这是我在Angular 1中非常熟悉的事情,但我无法在Angular 2中工作。我得到的错误是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([]));
}

3 个答案:

答案 0 :(得分:6)

感谢大家的帮助,让我看到了正确的区域。缺少的是导入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([]));
}

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

答案 1 :(得分:0)

这是因为这个

@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)

答案 2 :(得分:0)

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