在我的应用程序中,我有一个组件,它只是通过某些服务异步获取一些值并将其显示给用户。
组件的代码如下所示:
@Component({
changeDetection: ChangeDetectionStrategy.Default,
selector: 'test-component',
template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
private readonly someService = inject(SomeService);
myProperty: string;
ngOnInit(): void {
this.someService.getSomeDataAsync().subscribe((value) => {
this.myProperty = value;
});
}
}
并通过一个简单的测试进行了测试,如下所示:
it('should correctly display myProperty in the HTML template of the Component', () => {
const myMockedValue = 'Some mock value;'
const { fixture, component, page } = setup();
const someService = TestBed.inject(SomeService);
spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);
fixture.detectChanges();
expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});
到目前为止一切正常。现在,假设我想将
ChangeDetectionStrategy
更改为 OnPush
。由于myProperty
的值更新后未触发变化检测,组件将被破坏:
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'test-component',
template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
private readonly someService = inject(SomeService);
myProperty: string;
ngOnInit(): void {
this.someService.getSomeDataAsync().subscribe((value) => {
this.myProperty = value;
});
}
}
现在,有趣的是测试仍然通过。因为变化检测是由此触发的
fixture.detectChanges()
。
这是不一致的:现在我的测试告诉我一切正常,但实际上却并非如此。
我知道要修复此错误,我只需在组件中手动调用更改检测:
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'test-component',
template: `<div id="my-special-property">{{ myProperty }}</div>`,
})
export class DashboardComponent implements OnInit {
private readonly someService = inject(SomeService);
private readonly changeDetectorRef = inject(ChangeDetectorRef);
myProperty: string;
ngOnInit(): void {
this.someService.getSomeDataAsync().subscribe((value) => {
this.myProperty = value;
this.changeDetectorRef.detectChanges(); // this fixes the problem
});
}
}
但是通过此修复,如果我从测试中删除
fixture.detectChanges()
以尝试使其更真实,它将失败:
it('should correctly display myProperty in the HTML template of the Component', () => {
const myMockedValue = 'Some mock value;'
const { fixture, component, page } = setup();
const someService = TestBed.inject(SomeService);
spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);
// this will fail
expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});
我找到了一种让它变得现实的方法,这意味着只有当修复了this.changeDetectorRef.detectChanges()
时,测试才会
通过,而当它不存在时,测试就会失败。
这种方式是在测试中手动调用
component.ngOnInit()
,而不是fixture.detectChanges()
,但是我觉得有点丑:
it('should correctly display myProperty in the HTML template of the Component', () => {
const myMockedValue = 'Some mock value;'
const { fixture, component, page } = setup();
const someService = TestBed.inject(SomeService);
spyOn(someService, 'getSomeDataAsync').and.returnValue(myMockedValue);
component.ngOnInit();
expect(page.getMySpecialPropertyLocator().innerHTML).toContain(myMockedValue);
});
无需从单元测试中手动调用
component.ngOnInit()
来实现相同目标的正确方法是什么?
换句话说,在使用了OnPush
变更检测之后,如何确保单元测试是
现实并且真实地反映了组件在真实应用程序中的行为方式?
注意:这个例子是真实组件的简化版本,可以在这里找到:
https://github.com/azerothcore/Keira3/tree/master/src/app/features/dashboard
通常在处理
OnPush
时,您应该使用更具反应性的方法。
关键是要使用
AsyncPipe
。
简单来说,有两种可能的方法
AsyncPipe
订阅可观察的状态属性。.subscribe
) 分配(不带 SomeService#getSomeDataAsync
)到类属性 myProperty
。需要注意的是,如果不缓存,每次触发CD时,都会触发多次http调用。只需调用 pipe
并确保您使用 share
或 shareReplay
运算符缓存可观察值。一般而言,
OnPush
+ AsyncPipe
是有效好友。尝试一下!