我正在为Angular2中的服务构建一些单元测试。
在我的服务中,我有以下代码:
var hash: string;
hash = this.window.location.hash;
但是,当我运行包含此代码的测试时,它将失败。
利用Window的所有功能会很棒,但是当我使用PhantomJs时,我认为这是不可能的(我也尝试过Chrome,它会产生相同的效果)。
在AngularJs中,我会使用模拟$ Window(或者至少是有问题的属性),但由于Angular2单元测试没有很多文档,我不知道该怎么做。
有人可以帮忙吗?
答案 0 :(得分:21)
在Angular 2中,您可以使用@Inject()
函数通过使用字符串标记命名窗口对象来注入窗口对象,就像这样
constructor( @Inject('Window') private window: Window) { }
在@NgModule
中,您必须使用相同的字符串提供它:
@NgModule({
declarations: [ ... ],
imports: [ ... ],
providers: [ { provide: 'Window', useValue: window } ],
})
export class AppModule {
}
然后你也可以使用令牌字符串
来模拟它beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
这适用于Angular 2.1.1,最新截至2016-10-28。
不适用于Angular 4.0.0 AOT。 https://github.com/angular/angular/issues/15640
答案 1 :(得分:6)
正如@estus在评论中提到的那样,您最好从路由器获取哈希值。但是要直接回答你的问题,你需要在你正在使用它的地方注入窗口,以便在测试期间你可以模拟它。
首先,使用angular2提供程序注册窗口 - 如果你在整个地方使用它,可能在某个全局:
import { provide } from '@angular/core';
provide(Window, { useValue: window });
当依赖注入请求类型Window
时,这会告诉angular,它应该返回全局window
。
现在,在你正在使用它的地方,你将它注入你的课堂而不是直接使用全球:
import { Component } from '@angular/core';
@Component({ ... })
export default class MyCoolComponent {
constructor (
window: Window
) {}
public myCoolFunction () {
let hash: string;
hash = this.window.location.hash;
}
}
现在,您已准备好在测试中模拟该值。
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
injectAsync
} from 'angular2/testing';
let myMockWindow: Window;
beforeEachProviders(() => [
//Probably mock your thing a bit better than this..
myMockWindow = <any> { location: <any> { hash: 'WAOW-MOCK-HASH' }};
provide(Window, {useValue: myMockWindow})
]);
it('should do the things', () => {
let mockHash = myMockWindow.location.hash;
//...
});
答案 2 :(得分:3)
在RC4方法*mut std::os::raw::c_void
之后它被删除了,所以在RC4之后处理这个的方法是:
provide()
我需要一段时间来弄清楚它是如何工作的。
答案 3 :(得分:0)
我真的不明白为什么没人提供最简单的解决方案,即Angular-Team推荐的一种测试服务的方法,如您所见here。在大多数情况下,您甚至根本不需要处理TestBed的内容。
还有,您也可以将这种方法用于组件和指令。在这种情况下,您将不会创建组件实例,而是创建类实例。这意味着,您也不必处理组件模板中使用的子组件。
假设您能够将Window注入到构造函数中
constructor(@Inject(WINDOW_TOKEN) private _window: Window) {}
只需在.spec文件中执行以下操作:
describe('YourService', () => {
let service: YourService;
beforeEach(() => {
service = new YourService(
{
location: {hash: 'YourHash'} as any,
...
} as any,
...
);
});
}
我不在乎其他属性,因此我通常将类型强制转换添加到any
。还要包括所有其他属性,并适当键入。
如果您需要在模拟属性中使用不同的值,则可以仅监视它们并使用茉莉花的returnValue
来更改值:
const spy: any = spyOn((service as any)._window, 'location').and.returnValue({hash: 'AnotherHash'});
或
const spy: any = spyOn((service as any)._window.location, 'hash').and.returnValue('AnotherHash');