我在使用
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
重新呈现。我是不是错过了什么?
这个问题很容易解决... https://github.com/angular/angular/issues/12313#issuecomment-298697327
TestBed.configureTestingModule({
declarations: [ MyComponent ]
})
.overrideComponent(MyComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents();
请记住,这种方法可能会掩盖一些变更检测问题
学分:marchitos
您需要告诉 Angular 您更改了组件的输入属性。在理想的世界中,您会替换
component.isOwner = false;
fixture.detectChanges();
与
component.isOwner = false;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
不幸的是,这不起作用,因为 Angular 中存在一个错误(https://github.com/angular/angular/issues/12313)。您可以使用此处描述的解决方法之一。
它不起作用,因为你的装置中的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();
}
});
如果您查看了 @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 示例
修改 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();
});
类似于 @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();
有一些解决方案,但就您而言,我认为最简单的方法是将您的测试分成两个单独的测试。如果在每个测试中您调用
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();
});