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

Posted

技术标签:

【中文标题】使用 Jasmine 间谍进行 Angular 2 组件测试“没有 Http 提供者!”错误【英文标题】:Angular 2 component testing with Jasmine spies "No provider for Http!" error 【发布时间】: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([]));

【问题讨论】:

【参考方案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 对数据的作用,而不是测试某个方法被调用的事实。

【讨论】:

以上是关于使用 Jasmine 间谍进行 Angular 2 组件测试“没有 Http 提供者!”错误的主要内容,如果未能解决你的问题,请参考以下文章

创建间谍时,Jasmine找不到功能

Jasmine spyOn 函数和返回对象

如何为 Jasmine 间谍的多个调用提供不同的返回值

使用 Jasmine 进行 Angular 单元测试:如何删除或修改 spyOn

间谍服务在spyOn时变为未定义

Chutzpah - 使用 jasmine 和 TypeScript 进行 AngularJS 测试