Jasmine 测试中 AfterViewInit 的生命周期钩子

Posted

技术标签:

【中文标题】Jasmine 测试中 AfterViewInit 的生命周期钩子【英文标题】:Lifecycle hook of AfterViewInit in Jasmine test 【发布时间】:2017-12-15 18:11:37 【问题描述】:

我对与 Jasmine 测试相关的生命周期挂钩感到困惑。 LifeCycle Angular 文档没有提到测试https://angular.io/guide/lifecycle-hooks。测试文档仅提及 OnChange https://angular.io/guide/testing。 我有一个示例组件如下:

import  Component, OnInit, AfterViewInit, OnDestroy, ElementRef  from '@angular/core';
...
@Component(
  selector: 'app-prod-category-detail',
  templateUrl: './prod-category-detail.component.html',
  styleUrls: ['./prod-category-detail.component.css']
)
//
export class ProdCategoryDetailComponent implements OnInit, AfterViewInit, OnDestroy 
    ...
    nav: HTMLSelectElement;
    //
    constructor(
        ...
        private _elementRef: ElementRef )  
    ...
    ngAfterViewInit() 
        console.log( 'ProdCategoryDetailComponent: ngAfterViewInit' );
        this.nav = this._elementRef.nativeElement.querySelector('#nav');
    
    ...

请注意,这是一个 Angular CLI 应用程序,具有最新下载。在 Karma 中,我看不到控制台日志,因此永远不会设置 nav。我目前在我的规范中调用它如下:

beforeEach(() => 
  fixture = TestBed.createComponent(ProdCategoryDetailComponent);
  sut = fixture.componentInstance;
  sut.ngAfterViewInit( );
  fixture.detectChanges( );
);

这是正确的处理方式吗?

对于 shusson,这是很久以前的,我已经有一段时间没有看这个了。希望它会有所帮助。请注意,我使用的是 Primeface primeng 库:

describe('ProdCategoryDetailComponent', () => 
  let sut: ProdCategoryDetailComponent;
  let fixture: ComponentFixture< ProdCategoryDetailComponent >;
  let alertService: AlertsService;
  let prodCatService: ProdCategoryServiceMock;
  let confirmService: ConfirmationServiceMock;
  let elementRef: MockElementRef;
  //
  beforeEach(async(() => 
    TestBed.configureTestingModule(
      imports: [
        FormsModule,
        ButtonModule,
        BrowserAnimationsModule
      ],
      declarations: [
        ProdCategoryDetailComponent,
        AlertsComponent,
        ConfirmDialog
      ],
      providers: [
        AlertsService,
         provide: ProdCategoryService, useClass: ProdCategoryServiceMock ,
         provide: MockBackend, useClass: MockBackend ,
         provide: BaseRequestOptions, useClass: BaseRequestOptions ,
         provide: ConfirmationService, useClass: ConfirmationServiceMock ,
         provide: ElementRef, useClass: MockElementRef 
      ]
    )
    .compileComponents();
  ));
  //
  beforeEach(inject([AlertsService, ProdCategoryService,
      ConfirmationService, ElementRef],
        (srvc: AlertsService, pcsm: ProdCategoryServiceMock,
        cs: ConfirmationServiceMock, er: MockElementRef) => 
    alertService = srvc;
    prodCatService = pcsm;
    confirmService = cs;
    elementRef = er;
  ));
  //
  beforeEach(() => 
    fixture = TestBed.createComponent(ProdCategoryDetailComponent);
    sut = fixture.componentInstance;
    sut.ngAfterViewInit( );
    fixture.detectChanges( );
  );
  //

【问题讨论】:

ngAfterViewInit 应该使用第一个 fixture.detectChanges( ) 调用一次; 您能告诉我们您是如何配置 TestBed 的吗?例如TestBed.configureTestingModule(...)。另外你真的有任何测试吗?如果没有 it 测试,beforeEach 将不会被调用。 【参考方案1】:

我经常在必要时直接从每个规范中调用生命周期钩子。这有效。 因为这提供了在调用ngAfterViewInit()ngOnInit() 之前操作任何数据的灵活性。

我还看到很少有角度库测试规范以相同的方式使用它。例如,检查这个videogular 规范文件。所以手动调用这些方法并没有什么坏处。

这里也复制相同的代码,以免以后链接被破坏。

it('Should hide controls after view init', () => 
        spyOn(controls, 'hide').and.callFake(() => );

        controls.vgAutohide = true;

        controls.ngAfterViewInit();

        expect(controls.hide).toHaveBeenCalled();
);

【讨论】:

【参考方案2】:

我们不能直接从规范调用生命周期钩子,但我们可以调用自定义方法。因为要调用生命周期书籍,我们需要使用夹具创建组件的实例。

示例:在我的示例中,我需要将 padding-left 和 right 设置为 0,因为 bootstrap 默认情况下会左右提供一些填充,所以我需要将其删除。

HTML 文件 - app.component.ts

import  Component, ViewChild, ElementRef, Renderer2  from '@angular/core';

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
)
export class AppComponent 
  @ViewChild('navCol',  static: true ) navCol: ElementRef;

  constructor(private renderer: Renderer2)   

  ngAfterViewInit() 
    this.renderer.setStyle(this.navCol.nativeElement, 'padding-left', '0px');
    this.renderer.setStyle(this.navCol.nativeElement, 'padding-right', '0px');
  

app.component.spec.ts

import  TestBed, async  from '@angular/core/testing';

describe('AppComponent', () => 
  it('should load page and remove padding', () => 
    const fixture = TestBed.createComponent(AppComponent);
    fixture.componentInstance.ngAfterViewInit();
    const styles = window.getComputedStyle(fixture.nativeElement);

    expect(styles.paddingLeft).toBe('0px');
    expect(styles.paddingRight).toBe('0px');
  );
);

【讨论】:

嗨 Sahil Shikalgar,在这种情况下,view child 如何没有给你带来问题,你说你不必调用 fixture.ngAfterViewInit(); 谢谢@Sahil Shikalgar,我想我正在使用fixture.detectChanges(),即使我的测试没有断言HTML。我认为也许大多数人使用fixture.detectChanges() 的频率比他们必须的要多。 谢谢Girum,我也会用fixture.detectChanges()检查而不是手动调用。

以上是关于Jasmine 测试中 AfterViewInit 的生命周期钩子的主要内容,如果未能解决你的问题,请参考以下文章

将代码放在 Jasmine 测试 + Meteor 的正确目录中

如何在 Jasmine 测试中测试 $scope?

在 Jasmine 中访问 Meteor 模板辅助函数以进行集成测试

在Visual Studio 2013中使用Jasmine + Karma进行AngularJS测试

jasmine测试文件中是不是需要使用'use strict'的函数形式?

单元测试 jasmine