ユニットテストを開始したばかりで、自分のサービスとAngularとIonicの一部)をモックすることができましたが、 ChangeDetectorRef
は同じままです。
これはどの種類の魔術ですか?
_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 ??
})
));
_
_ it('fails no matter what', async(() => {
spyOn(cdRef, 'markForCheck');
spyOn(cmp.cdRef, 'markForCheck');
cmp.ngOnInit();
expect(cdRef.markForCheck).toHaveBeenCalled(); // fail, why ??
expect(cmp.cdRef.markForCheck).toHaveBeenCalled(); // success
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
}));
_
_@Component({
...
})
export class MyComponent {
constructor(private cdRef: ChangeDetectorRef){}
ngOnInit() {
// do something
this.cdRef.markForCheck();
}
}
_
私はすべて、async
、fakeAsync
、injector([ChangeDetectorRef], () => {})
を試しました。
何も機能しません。
誰かがこれに遭遇した場合、これは私にとってうまくいった1つの方法です:
コンストラクタにChangeDetectorRefインスタンスを挿入しているので、
_ constructor(private cdRef: ChangeDetectorRef) { }
_
コンポーネントのプライベート属性の1つとしてcdRef
があります。つまり、コンポーネントをスパイし、その属性をスタブして、必要なものを返すようにすることができます。また、必要に応じて、その呼び出しとパラメーターをアサートできます。
スペックファイルでは、ChangeDetectorRefを提供せずにTestBedを呼び出します。これは、提供するものを提供しないためです。 beforeEachブロックと同じコンポーネントを設定して、ドキュメントで行われるように仕様間でリセットされるようにします here :
_component = fixture.componentInstance;
_
次に、テストで属性を直接スパイします
_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
がプライベート属性であるため使用されていることに注意してください。しかし、privateは実際のコンパイル済みJavaScriptには存在しないため、アクセス可能です。
実行時にテストのためにその方法でプライベート属性にアクセスするかどうかは、あなた次第です。個人的には問題ありませんが、スペックに基づいてカバー範囲を広げています。
おそらく指摘する必要がある1つのポイントは、本質的にここで変更検出器自体(Angularチームによってテストされた)を単体テストするのではなく、独自のコードをテストすることです。私の意見では、これは変更検出器への呼び出しをローカルのプライベートメソッドに抽出する必要があることを示す適切なインジケーターです(単体テストはしたくないのでプライベートです)。
private detectChanges(): void {
this.cdRef.detectChanges();
}
次に、単体テストで、コードが実際にこの関数を呼び出し、したがってChangeDetectorRefからメソッドを呼び出したことを確認します。例えば:
it('should call the change detector',
() => {
const spyCDR = spyOn((cmp as any), 'detectChanges' as any);
cmp.ngOnInit();
expect(spyCDR).toHaveBeenCalled();
}
);
私はまったく同じ状況でした。これは、ユニットテストでは実際にこのパターンによってコードをより適切に構造化することを強制されているとシニアデベロッパーからユニットテストの一般的なベストプラクティスとして提案されました。提案された再構築により、コードを変更に柔軟に対応させることができます。 Angularが変更検出を提供する方法を変更する場合は、detectChangesメソッドを調整するだけで済みます。
これが新しいものかどうかはわかりませんが、changeDetectorRefはフィクスチャを介してアクセスできます。
ドキュメントを参照してください: https://angular.io/guide/testing#componentfixture-properties
変更検出器のモッキングで同じ問題に遭遇し、これが解決策になりました
単体テストの場合、作成するコンポーネントの依存性注入を満たすためだけにChangeDetectorRef
をモックする場合、任意の値を渡すことができます。
私の場合、私はこれをしました:
TestBed.configureTestingModule({
providers: [
FormBuilder,
MyComponent,
{ provide: ChangeDetectorRef, useValue: {} }
]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)
myComponent
が正常に作成されます。テスト実行パスにChangeDetectorRef
が必要ないことを確認してください。使用する場合は、useValue: {}
を適切なモックオブジェクトに置き換えます。
私の場合、FormBuilder
を使用してフォーム作成に関するものをテストする必要がありました。