9

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

user2832323
  • 163
  • 2
  • 7

4 Answers4

4

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:

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    Note that in Angular 5 this solution seems to no longer work. However I've found that `(MyComponent as any)['__prop__metadata__'].child[0].selector = ChildDirectiveMock;` should work but it doesn't feel like a very good permanent solution. – joshhunt Jan 09 '18 at 03:20
  • Thanks. (MyComponent as any)['__prop__metadata__'].child[0].selector = ChildDirectiveMock; is working – drj Apr 21 '20 at 17:15
1

if u just want to test the function in child component are called or not u can try this

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
    });
  });
Mirza Setiyono
  • 181
  • 3
  • 10
1

See this question for a discussion of how to resolve this issue with Angular 10 (since none of the techniques described here work beyond Angular 9).

Gary McGill
  • 26,400
  • 25
  • 118
  • 202
0

Note that in Angular 5 this solution seems to no longer work. However joshhunt has found that (MyComponent as any)['prop__metadata'].child[0].selector = ChildDirectiveMock; should work but it doesn't feel like a very good permanent solution. – joshhunt

drj
  • 193
  • 1
  • 6