Angular2(TypeScript)中的单元测试/模拟窗口属性

Posted

技术标签:

【中文标题】Angular2(TypeScript)中的单元测试/模拟窗口属性【英文标题】:Unit Testing/mocking Window properties in Angular2 (TypeScript) 【发布时间】:2016-08-02 19:24:24 【问题描述】:

我正在为 Angular2 中的服务构建一些单元测试。

在我的服务中,我有以下代码:

var hash: string; hash = this.window.location.hash;

但是,当我运行包含此代码的测试时,它会失败。

利用 Window 的所有功能会很棒,但由于我使用的是 PhantomJs,我认为这是不可能的(我也尝试过 Chrome 产生相同的结果)。

在 AngularJs 中,我会求助于模拟 $Window(或至少是有问题的属性),但由于 Angular2 单元测试的文档不多,我不知道该怎么做。

有人可以帮忙吗?

【问题讨论】:

好像是quite straghtforward。可能是XY问题,因为路由器已经has the hash abstracted,抽象上升到DOM location。 【参考方案1】:

在 Angular 2 中,您可以使用 @Inject() 函数通过使用字符串标记命名窗口对象来注入窗口对象,如下所示

  constructor( @Inject('Window') private window: Window)  

@NgModule 中,您必须使用相同的字符串提供它:

@NgModule(
    declarations: [ ... ],
    imports: [ ... ],
    providers: [  provide: 'Window', useValue: window  ],
)
export class AppModule 

那么你也可以使用token字符串来模拟它

beforeEach(() => 
  let windowMock: Window = <any> ;
  TestBed.configureTestingModule(
    providers: [
      ApiUriService,
       provide: 'Window', useFactory: (() =>  return windowMock; ) 
    ]
  );

这在 2016 年 10 月 28 日最新的 Angular 2.1.1 中有效。

不适用于 Angular 4.0.0 AOT。 https://github.com/angular/angular/issues/15640

【讨论】:

这适用于测试,但不适用于 AoT 编译(编译但带有警告并且应用程序在浏览器中崩溃) 使用 AoT 构建尝试过此操作(以及在构造函数中将 window 键入为 any 以解决不同的错误),但是当我尝试访问自定义属性时,我的生产构建崩溃了window 我在另一个文件中设置。我在这里遵循了解决方案,并且它起作用了:***.com/a/37176929/1683187 这是在需要时在每个测试中动态更改 windowMock 对象的正确答案【参考方案2】:

正如评论中提到的@estus,您最好从路由器获取哈希。但是要直接回答您的问题,您需要将 window 注入您正在使用它的地方,以便在测试期间您可以模拟它。

首先,向 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;
    //...
);

【讨论】:

您也可以只注入constructor(window:Window) 并像provide(Window, useValue: window)provide(Window, useClass: MyWindowMock) 一样提供它。如果有可用类型,则无需使用字符串键。 provide removed in RC6 从核心,将其更改为 providers: [ provide: Window, useValue: window, ] 这是救命稻草。使我能够在没有页面重定向的情况下模拟我需要的测试。在 Angular 11 中工作【参考方案3】:

在RC4方法provide()被弃用之后,RC4之后的处理方法是:

  let myMockWindow: Window;

  beforeEach(() => 
    myMockWindow = <any>  location: <any> hash: 'WAOW-MOCK-HASH';
    addProviders([SomeService, provide: Window, useValue: myMockWindow]);
  );

我花了一段时间才弄清楚它是如何工作的。

【讨论】:

该语法在 Angular2 的发布版本中已被弃用【参考方案4】:

我真的不明白为什么没有人提供最简单的解决方案,即 Angular 团队推荐的测试服务的方法,如您所见 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。随意包含所有其他属性并正确键入。

如果您需要模拟属性的不同值,您可以简单地监视它们并使用 jasmine 的 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');

【讨论】:

【参考方案5】:

带有内置工厂的注入令牌似乎是要走的路。

我将这些用于任何全局浏览器,如窗口、文档、localStorage、控制台等。

core/providers/window.provider.ts

import  InjectionToken  from '@angular/core';

export const WINDOW = new InjectionToken<Window>(
    'Window',
    
        providedIn: 'root',
        factory(): Window 
            return window;
        
    
);

注入:

constructor(@Inject(WINDOW) private window: Window)

单元测试:

const mockWindow = 
  setTimeout: jest.fn(),
  clearTImeout: jest.fn()
;

TestBed.configureTestingModule(
  providers: [
    
      provide: WINDOW,
      useValue: mockWindow
    
  ]
);

【讨论】:

以上是关于Angular2(TypeScript)中的单元测试/模拟窗口属性的主要内容,如果未能解决你的问题,请参考以下文章

angular2 Typescript中的Jquery不起作用

如何从 Angular2(Typescript)中的 Json 数组中获取值的总和

如何使用 Jasmine 为私有方法编写 Angular / TypeScript 单元测试

给 TypeScript 项目编写单元测试的一些小经验

通过 Webpack(终端)执行 Typescript Jasmine 测试以测试 Angular2

单击 InfoWindow Typescript Angular2