使用 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 提供者!”错误的主要内容,如果未能解决你的问题,请参考以下文章