[英]Angular 2: How to mock ChangeDetectorRef while unit testing
我刚刚开始进行单元测试,我已经能够模拟我自己的服务以及一些 Angular 和 Ionic,但无论我做什么,
ChangeDetectorRef<\/code>都保持不变。
我的意思是这是什么魔法?
beforeEach(async(() =>
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [
Form, DomController, ToastController, AlertController,
PopoverController,
{provide: Platform, useClass: PlatformMock},
{
provide: NavParams,
useValue: new NavParams({data: new PageData().Data})
},
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock}
],
imports: [
FormsModule,
ReactiveFormsModule,
IonicModule
],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
],
viewProviders: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
]
}
})
.compileComponents()
.then(() => {
let fixture = TestBed.createComponent(MyComponent);
let cmp = fixture.debugElement.componentInstance;
let cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
})
));
2020 年更新:
我最初是在 2017 年 5 月写的,这是一个在当时非常有效并且仍然有效的解决方案。
我们无法通过测试台配置 changeDetectorRef 模拟的注入,所以这就是我最近在做的事情:
it('detects changes', () => {
// This is a unique instance here, brand new
const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef);
// So, I am spying directly on the prototype.
const detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');
component.someMethod(); // Which internally calls the detectChanges.
expect(detectChangesSpy).toHaveBeenCalled();
});
那么你不关心私有属性或任何。
万一有人遇到这种情况,这是一种对我很有效的方法:
当您在构造函数中注入 ChangeDetectorRef 实例时:
constructor(private cdRef: ChangeDetectorRef) { }
您将cdRef
作为组件的私有属性之一,这意味着您可以监视组件,存根该属性并让它返回您想要的任何内容。 此外,您可以根据需要断言其调用和参数。
在您的规范文件中,在不提供 ChangeDetectorRef 的情况下调用您的 TestBed,因为它不会提供您提供的内容。 在每个块之前设置相同的组件,因此它会在规范之间重置,就像在此处的文档中所做的那样:
component = fixture.componentInstance;
然后在测试中,直接对属性进行spy
describe('someMethod()', () => {
it('calls detect changes', () => {
const spy = spyOn((component as any).cdRef, 'detectChanges');
component.someMethod();
expect(spy).toHaveBeenCalled();
});
});
对于间谍,您可以使用.and.returnValue()
并让它返回您需要的任何内容。
注意(component as any)
被用作cdRef
是一个私有属性。 但是私有在实际编译的javascript中不存在,所以它是可以访问的。
如果您想在运行时以这种方式访问私有属性以进行测试,这取决于您。
不确定这是否是新事物,但可以通过夹具访问 changeDetectorRef。
请参阅文档: https : //angular.io/guide/testing#componentfixture-properties
我们在更改检测器模拟方面遇到了同样的问题,这最终成为了解决方案
可能需要指出的一点是,本质上您想在这里测试自己的代码,而不是对变更检测器本身进行单元测试(由 Angular 团队测试)。 在我看来,这是一个很好的指标,表明您应该将对变更检测器的调用提取到本地私有方法(私有,因为它是您不想进行单元测试的东西),例如
private detectChanges(): void {
this.cdRef.detectChanges();
}
然后,在您的单元测试中,您需要验证您的代码是否实际调用了此函数,从而调用了 ChangeDetectorRef 中的方法。 例如:
it('should call the change detector',
() => {
const spyCDR = spyOn((cmp as any).cdRef, 'detectChanges' as any);
cmp.ngOnInit();
expect(spyCDR).toHaveBeenCalled();
}
);
我遇到了完全相同的情况,这是一位高级开发人员向我建议的单元测试的一般最佳实践,他告诉我单元测试实际上迫使您通过这种模式更好地构建代码。 通过建议的重组,您可以确保您的代码可以灵活更改,例如,如果 Angular 更改了它们为我们提供更改检测的方式,那么您只需调整 detectChanges 方法。
对于单元测试,如果您只是为了满足要创建的组件的依赖注入而ChangeDetectorRef
,则可以传入任何值。
对于我的情况,我这样做了:
TestBed.configureTestingModule({
providers: [
FormBuilder,
MyComponent,
{ provide: ChangeDetectorRef, useValue: {} }
]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)
它将成功创建myComponent
。 只要确保测试执行路径不需要ChangeDetectorRef
。 如果你这样做,那么用适当的模拟对象替换useValue: {}
。
就我而言,我只需要使用FormBuilder
测试一些表单创建内容。
// component
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public someHandler() {
this.changeDetectorRef.detectChanges();
}
// spec
const changeDetectorRef = fixture.componentRef.changeDetectorRef;
jest.spyOn(changeDetectorRef, 'detectChanges');
fixture.detectChanges(); // <--- needed!!
component.someHandler();
expect(changeDetectorRef.detectChanges).toHaveBeenCalled();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.