Search code examples
angularangular2-directivesangular2-testing

How to replace a component used in @viewChildren for a test double?


Suppose I have a component I want to test that uses a very complex component. Furthermore it calls some of its methods using references obtained by @viewChildren. For example

    @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>
    // ....
    }

How can I replace the complex-component for a test double in `TestBed'?

Something like

@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);
});

    //....
}));

For a full example see the plnkr http://plnkr.co/edit/ybdrN8VimzktiDCTvhwe?p=preview


Solution

  • You can use reflect-metadata features to do it working:

    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
    });
    

    Test in Plunker

    You can read more about decorators and about reflect-metadata here: