在 Angular 中测试 OnPush 组件

问题描述 投票:0回答:7

我在使用

OnPush
更改检测策略测试组件时遇到问题。

测试是这样的

it('should show edit button for featured only for owners', () => {
    let selector = '.edit-button';

    component.isOwner = false;
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css(selector))).toBeFalsy();

    component.isOwner = true;
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css(selector))).toBeTruthy();
});

如果我使用

Default
策略,它会按预期工作,但使用
OnPush
时,对
isOwner
的更改不会通过调用
detectChanges
重新呈现。我是不是错过了什么?

angular testing angular-cli
7个回答
13
投票

这个问题很容易解决... https://github.com/angular/angular/issues/12313#issuecomment-298697327

TestBed.configureTestingModule({
  declarations: [ MyComponent ] 
})
.overrideComponent(MyComponent, {
  set: {  changeDetection: ChangeDetectionStrategy.Default  }
})
.compileComponents();

请记住,这种方法可能会掩盖一些变更检测问题

学分:marchitos


5
投票

您需要告诉 Angular 您更改了组件的输入属性。在理想的世界中,您会替换

component.isOwner = false;
fixture.detectChanges();

component.isOwner = false;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();

不幸的是,这不起作用,因为 Angular 中存在一个错误(https://github.com/angular/angular/issues/12313)。您可以使用此处描述的解决方法之一。


5
投票

它不起作用,因为你的装置中的changeDetectorRef与你的组件中的不同。摘自 Angular 中的问题:

“...ComponentRef 上的changeDetectorRef 指向动态创建的组件的根(主机)视图的更改检测器。然后,在主机视图内我们得到了实际的组件视图,但组件视图是 OnPush 因此我们从不刷新它!” - 来源

选项 A. 解决此问题的一种方法是使用组件注入器来获取 realchangeDetectionRef:

describe('MyComponent', () => {
  let fixture;
  let component;

  beforeEach(() => {
    TestBed.configureTestingModule({ ... }).compileComponents();
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('does something', () => {
    // set the property here
    component.property = 'something';

    // do a change detection on the real changeDetectionRef
    fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges();

    expect(...).toBe(...);
  });
});

您也可以只使用到 @Input 的初始绑定(它最初触发 OnPush 策略的变更检测):

选项B1:

describe('MyComponent', () => {
  let fixture;
  let component;

  beforeEach(() => {
    TestBed.configureTestingModule({ ... }).compileComponents();
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
  });

  it('does something', () => {
    // set the property here
    component.property = 'something';

    // do the first (and only) change detection here
    fixture.detectChanges();

    expect(...).toBe(...);
  });
});

或者例如:

选项B2:

describe('MyComponent', () => {
  let fixture;
  let component;

  it('does something', () => {
    // set the property here
    setup({ property: 'something' });
    expect(...).toBe(...);
  });

  function setup(props: { property? } = {}) {
    TestBed.configureTestingModule({ ... }).compileComponents();
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;

    Object.getOwnPropertyNames(props).forEach((propertyName) => {
      component[propertyName] = props[propertyName];
    });

    // do the first (and only) change detection here
    fixture.detectChanges();
  }
});

4
投票

如果您查看了 @Günter 的这个很棒的答案 Angular 2 更改检测和 ChangeDetectionStrategy.OnPush 那么您可以使用事件处理程序来解决它,例如:

const fixture = TestBed.overrideComponent(TestComponent, {set: {host: { "(click)": "dummy" }}}).createComponent(TestComponent);
// write your test
fixture.debugElement.triggerEventHandler('click', null);
fixture.detectChanges();

这是 Plunker 示例


4
投票

推动新状态的边缘情况

修改 TypeScript 代码中的输入属性。当您使用@ViewChild或@ContentChild等API来获取对TypeScript中组件的引用并手动修改@Input属性时,Angular不会自动运行OnPush组件的更改检测。如果您需要 Angular 运行更改检测,您可以在组件中注入 ChangeDetectorRef 并调用 changeDetectorRef.markForCheck() 来告诉 Angular 安排更改检测。

所以根据https://github.com/angular/angular/pull/46641最佳实践是使用

setInput
方法:
fixture.componentRef.setInput()
, 因此,为了改进我们的代码,我们可以利用
Typescript
并创建一个全局函数来处理它。

function  setInput<T>(fixture: ComponentFixture<T>, prop: keyof T, value: T[keyof T]): void {
   fixture.componentRef.setInput(prop.toString(), value);
   fixture.detectChanges();
}

然后在我们的代码中使用它,如下所示:

  it('should show thumbnail when thumbnail input is filled', function () {
    setInput(fixture, 'thumbnailUrl', 'test/thumbnail.png');

    expect(fixure.debugElement.query(By.css('test'))).toBeTruthy();
  });

2
投票

类似于 @michaelbromley 公开 ChangeDetectionRef 所做的工作,但由于这仅用于测试,我只是使用 v2.6 中的 // @ts-ignore

 标志关闭了下一行的 
TypeScript 错误,这样我就可以保留参考私人的。

其工作原理的示例:

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { WidgetComponent } from './widget.component';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<my-widget *ngIf="widgetEnabled"></my-widget>`,
});
export class PushyComponent {
  @Input() widgetEnabled = true;

  constructor(private cdr: ChangeDetectorRef) {}

  // methods that actually use this.cdr here...
}

TestBed.configureTestingModule({
  declarations: [ PushyComponent, WidgetComponent ],
}).compileComponents();

const fixture = TestBed.createComponent(PushyComponent);
const component = fixture.componentInstance;
fixture.detectChanges();

expect(component.widgetEnabled).toBe(true);
let el = fixture.debugElement.query(By.directive(WidgetComponent));
expect(el).toBeTruthy();

component.widgetEnabled = false;
// @ts-ignore: for testing we need to access the private cdr to detect changes
component.cdr.detectChanges();
el = fixture.debugElement.query(By.directive(WidgetComponent));
expect(el).toBeFalsy();

1
投票

有一些解决方案,但就您而言,我认为最简单的方法是将您的测试分成两个单独的测试。如果在每个测试中您调用

fixture.detectChanges()
函数仅一次,那么一切都应该正常工作。

示例:

it('should hide edit button if not owner', () => {
    let selector = '.edit-button';

    component.isOwner = false;
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css(selector))).toBeFalsy();
});

it('should show edit button for owner', () => {
    let selector = '.edit-button';

    component.isOwner = true;
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css(selector))).toBeTruthy();
});
© www.soinside.com 2019 - 2024. All rights reserved.