Kjetil Watnedal Kjetil Watnedal - 3 months ago 135
TypeScript Question

Angular 2 unit testing component, mocking ContentChildren

I am implementing a wizard component in Angular 2 RC4, and now I am trying to write som unit tests. Unit testing in Angular 2 is starting to get well documented, but I simply cannot find out how to mock the result of a content query in the component.

The app has 2 components (in addition to the app component), WizardComponent and WizardStepComponent. The app component (app.ts) defines the wizard and the steps in its template:

<div>
<fa-wizard>
<fa-wizard-step stepTitle="First step">step 1 content</fa-wizard-step>
<fa-wizard-step stepTitle="Second step">step 2 content</fa-wizard-step>
<fa-wizard-step stepTitle="Third step">step 3 content</fa-wizard-step>
</fa-wizard>
</div>


The WizardComponent (wizard-component.ts) gets a reference to the steps by using a ContentChildren query.

@Component({
selector: 'fa-wizard',
template: `<div *ngFor="let step of steps">
<ng-content></ng-content>
</div>
<div><button (click)="cycleSteps()">Cycle steps</button></div>`

})
export class WizardComponent implements AfterContentInit {
@ContentChildren(WizardStepComponent) steps: QueryList<WizardStepComponent>;
....
}


The problem is how to mock the steps variable in the unit test:

describe('Wizard component', () => {
it('should set first step active on init', async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb
.createAsync(WizardComponent)
.then( (fixture) =>{
let nativeElement = fixture.nativeElement;
let testComponent: WizardComponent = fixture.componentInstance;

//how to initialize testComponent.steps with mock data?

fixture.detectChanges();

expect(fixture.componentInstance.steps[0].active).toBe(true);
});
})));
});


I have created a plunker implementing a very simple wizard demonstrating the problem. The wizard-component.spec.ts file contains the unit test.

If anyone can point me in the right direction, I would greatly appreciate it.

Answer

Thanks to drewmoore's answer in this question, I have been able to get this working.

The key is to create a wrapper component for testing, which specifies the wizard and the wizard steps in it's template. Angular will then do the content query for you and populate the variable.

Edit: Implementation is for angular 2 rc4

My full test implementation looks like this:

import { beforeEachProviders, describe, inject, async, it } from '@angular/core/testing';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { WizardComponent } from './wizard.directive';
import { WizardStepComponent } from './wizard-step.directive';
import { Component, provide, Output, EventEmitter, QueryList } from '@angular/core';

//We need to wrap the WizardComponent in this component when testing, to have the wizard steps initialized
@Component({
selector: 'test-cmp',
template: `<fa-wizard>
            <fa-wizard-step stepTitle="step1"></fa-wizard-step>
            <fa-wizard-step stepTitle="step2"></fa-wizard-step>
            <fa-wizard-step stepTitle="step3"></fa-wizard-step>
        </fa-wizard>`,
directives: [ WizardComponent, WizardStepComponent ]
})
class TestWrapperComponent { 
}

describe('Wizard component', () => {
 it('should set first step active on init', async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
    return tcb
    .createAsync(TestWrapperComponent)
    .then( (fixture) =>{

        //get a reference to the component we actually want to test
        let wizardComponentInstance: WizardComponent = <WizardComponent>fixture.debugElement.children[ 0 ].componentInstance;

        fixture.detectChanges();

        expect(wizardComponentInstance.steps[0].active).toBe(true);
        expect(wizardComponentInstance.steps.length).toBe(3);
    });
  })));
});

If you have better/other solutions, you are very welcome to add you answer as well. I'll leave the question open for some time.

Comments