如何替换@viewChildren 中用于测试替身的组件?
How to replace a component used in @viewChildren for a test double?
假设我有一个要测试的组件,它使用了一个非常复杂的组件。此外,它使用 @viewChildren
获得的引用来调用它的一些方法。例如
@Component({
moduleId: module.id,
selector: 'test',
template: '<complex *ngFor='let v of vals'></complex>' ,
})
export class TestComponent{
vals = [1,2,3,4]
@ViewChildren(ComplexComponent) cpxs : QueryList<ComplexComponent>
// ....
}
如何在“TestBed”中替换测试替身的复杂组件?
类似
@Component({
moduleId : module.id,
selector: 'complex', template: ''
})
class ComplexComponentStub {
}
describe('TestComponent', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations : [ComplexComponentStub, TestComponent],
});
it('should have four sons',()=>{
let fixture = TestBed.createComponent(TestComponent);
let comp = fixture.componentInstance as TestComponent;
fixture.detectChanges();
expect(comp.cpxs.length).toBe(4);
});
//....
}));
有关完整示例,请参阅 plnkr
http://plnkr.co/edit/ybdrN8VimzktiDCTvhwe?p=preview
您可以使用反射元数据功能使其正常工作:
it('should have four sons', () => {
const propMetadata = Reflect['getMetadata']('propMetadata', FatherComponent);
var originType = propMetadata.cpxs[0].selector;
propMetadata.cpxs[0].selector = ComplexComponentStub; // Replace ViewChild Type
let fixture = TestBed.createComponent(FatherComponent);
let comp = fixture.componentInstance as FatherComponent;
fixture.detectChanges();
expect(comp.cpxs.length).toBe(4);
propMetadata.cpxs[0].selector = originType; // reset ViewChild
});
您可以在此处阅读有关装饰器和反射元数据的更多信息:
请注意,在 Angular 5 中,此解决方案似乎不再有效。然而 joshhunt 发现 (MyComponent as any)['prop__metadata'].child[0].selector = ChildDirectiveMock;应该可以,但感觉不是一个很好的永久解决方案。 – 乔什亨特
如果你只是想测试子组件中的函数是否被调用,你可以试试这个
component.ts
@ViewChildren('childComponent') childComponents: QueryList<YourComponent>;
component.spec.ts
it('Your test name', () => {
component.dashboard = dashboardMock; // ur using ngFor so u need to populate it first. u can mock it with ur own data
fixture.detectChanges(); // component will render the ngfor
const spies = [];
component.childComponents.toArray().forEach((comp) => {
comp.childFunction = () => { // Mock the function.
return 'Child function called!';
};
const tempSpy = {
spyKey: spyOn(comp, 'functionToBeMocked') // spy the mocked function
};
spies.push(tempSpy); // add to array
});
component.functionToTest(); // call the function u wish to test
spies.forEach((spy) => {
expect(spy.spyKey).toHaveBeenCalled(); // check if child function are called
});
});
有关如何使用 Angular 10 解决此问题的讨论,请参阅 this question(因为此处描述的 none 技术超出了 Angular 9)。
假设我有一个要测试的组件,它使用了一个非常复杂的组件。此外,它使用 @viewChildren
获得的引用来调用它的一些方法。例如
@Component({
moduleId: module.id,
selector: 'test',
template: '<complex *ngFor='let v of vals'></complex>' ,
})
export class TestComponent{
vals = [1,2,3,4]
@ViewChildren(ComplexComponent) cpxs : QueryList<ComplexComponent>
// ....
}
如何在“TestBed”中替换测试替身的复杂组件?
类似
@Component({
moduleId : module.id,
selector: 'complex', template: ''
})
class ComplexComponentStub {
}
describe('TestComponent', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations : [ComplexComponentStub, TestComponent],
});
it('should have four sons',()=>{
let fixture = TestBed.createComponent(TestComponent);
let comp = fixture.componentInstance as TestComponent;
fixture.detectChanges();
expect(comp.cpxs.length).toBe(4);
});
//....
}));
有关完整示例,请参阅 plnkr http://plnkr.co/edit/ybdrN8VimzktiDCTvhwe?p=preview
您可以使用反射元数据功能使其正常工作:
it('should have four sons', () => {
const propMetadata = Reflect['getMetadata']('propMetadata', FatherComponent);
var originType = propMetadata.cpxs[0].selector;
propMetadata.cpxs[0].selector = ComplexComponentStub; // Replace ViewChild Type
let fixture = TestBed.createComponent(FatherComponent);
let comp = fixture.componentInstance as FatherComponent;
fixture.detectChanges();
expect(comp.cpxs.length).toBe(4);
propMetadata.cpxs[0].selector = originType; // reset ViewChild
});
您可以在此处阅读有关装饰器和反射元数据的更多信息:
请注意,在 Angular 5 中,此解决方案似乎不再有效。然而 joshhunt 发现 (MyComponent as any)['prop__metadata'].child[0].selector = ChildDirectiveMock;应该可以,但感觉不是一个很好的永久解决方案。 – 乔什亨特
如果你只是想测试子组件中的函数是否被调用,你可以试试这个
component.ts
@ViewChildren('childComponent') childComponents: QueryList<YourComponent>;
component.spec.ts
it('Your test name', () => {
component.dashboard = dashboardMock; // ur using ngFor so u need to populate it first. u can mock it with ur own data
fixture.detectChanges(); // component will render the ngfor
const spies = [];
component.childComponents.toArray().forEach((comp) => {
comp.childFunction = () => { // Mock the function.
return 'Child function called!';
};
const tempSpy = {
spyKey: spyOn(comp, 'functionToBeMocked') // spy the mocked function
};
spies.push(tempSpy); // add to array
});
component.functionToTest(); // call the function u wish to test
spies.forEach((spy) => {
expect(spy.spyKey).toHaveBeenCalled(); // check if child function are called
});
});
有关如何使用 Angular 10 解决此问题的讨论,请参阅 this question(因为此处描述的 none 技术超出了 Angular 9)。