Can TestComponentBuilder test an angular component

2019-08-24 03:31发布

问题:

So, I'm trying to write a unit test for my component. My component is a dynamically created list, and when a user clicks certain items, I'd like my test to validate that the component generates outputs correctly.

To test this, I wrote a demo component that creates my component within its template, and then attempted to get TestComponentBuilder to create this demo component and click items on the lists. Unfortunately, my unit test cases can't find my component's html elements to click them. I can find the demo component's elements, but it seems like the test is having trouble accessing my nested component.

My question is: Can I get this approach to work? Or is there a better way to unit test my component's inputs and outputs?

Any help would be appreciated. Here's what I'm doing:

<== UPDATED per Gunter's Answer ==>

CustomListComponent.ts (My component):

import {Component, EventEmitter} from "angular2/core";
import {CustomListItem} from "./CustomListItem";

@Component({
    selector: 'custom-list-component',
    inputs: ['itemClass', 'items'],
    outputs: ['onChange'],
    template: `
    <ul class="list-unstyled">
      <li *ngFor="#item of items" [hidden]="item.hidden">
        <div [class]="itemClass" (click)="onItemClicked(item)">
            {{item.text}}
        </div>
      </li>
    </ul>
    `
})
export class CustomListComponent {
    itemClass: String;
    items: CustomListItem[];
    onChange: EventEmitter<CustomListItem>;

    constructor() {
        this.items = new Array<CustomListItem>();
        this.onChange = new EventEmitter();
    }

    onItemClicked(item: CustomListItem): void {
        let clone = new CustomListItem(item.text, item.hidden);
        this.onChange.emit(clone);
    }
}

CustomListItem.ts:

export class CustomListItem {
    text: String;
    hidden: boolean;

    constructor(text: String, hidden: boolean) {
        this.text = text;
        this.hidden = hidden;
    }
}

CustomListSpec.ts (Here's where I'm struggling):

import {CustomListComponent} from './CustomListComponent';
import {ComponentFixture, describe, expect, fakeAsync, inject, injectAsync, it, TestComponentBuilder} from 'angular2/testing'
import {Component} from "angular2/core";
import {CustomListItem} from "./CustomListItem";
import {By} from "angular2/src/platform/dom/debug/by";

describe('CustomListComponent', () => {
    var el;
    var dropdownToggleBtn, dropdownListEl;
    var regularListEl;

    it('Event output properly when list item clicked', injectAsync([TestComponentBuilder], (tcb) => {
        return createComponent(tcb).then((fixture) => {
            console.log("el (" + el + ")"); // => [object HTMLDivElement]
            console.log("dropdownToggleBtn (" + dropdownToggleBtn + ")"); // => [object HTMLButtonElement]
            console.log("dropdownListContainer(" + dropdownListContainer + ")"); // => [object HTMLDivElement]
            console.log("dropdownListEl(" + dropdownListEl + ")"); // => [object HTMLUListElement]
            //console.log("regularListEl (" + regularListEl+ ")");

            //...further testing...
        });
    }));

    function createComponent(tcb: TestComponentBuilder): Promise<ComponentFixture> {
        return tcb.createAsync(CustomListComponentDemo).then((fixture) => {
            fixture.detectChanges();

            el = fixture.debugElement.nativeElement;
            dropdownToggleBtn = fixture.debugElement.query(By.css(".btn")).nativeElement;
            dropdownListContainer = fixture.debugElement.query(By.css(".dropdown-menu")).nativeElement;
            dropdownListEl = dropdownListContainer.children[0].children[0];
            //regularListEl = fixture.debugElement.query(By.css(".list-unstyled")); //TIMES OUT unfortunately. Maybe because there are 2.

            return fixture;
        })
    }
});

@Component({
    directives: [CustomListComponent],
    template: `
        <div class="btn-group" dropdown>
            <button id="dropdown-toggle-id" type="button" class="btn btn-light-gray" dropdownToggle>
                <i class="glyphicon icon-recent_activity dark-green"></i> Dropdown <span class="caret"></span>
            </button>
            <div class="dropdown-menu" role="menu" aria-labelledby="dropdown-toggle-id">
            <custom-list-component id="dropdown-list-id"
                 [items]="dropdownListItems" [itemClass]="'dropdown-item'"
                 (onChange)="onDropdownListChange($event)">
            </custom-list-component>
        </div>
        <span class="divider">&nbsp;</span>
    </div>
    <custom-list-component id="regular-list-id"
        [items]="regularListItems"
        (onChange)="onRegularListChange($event)">
    </custom-list-component>
    `
})
class CustomListComponentDemo {
    dropdownListItems: CustomListItem[];
    regularListItems: CustomListItem[];

    constructor() {
        //intialize the item lists
    }

    onDropdownListChange(item: CustomListItem): void {
        //print something to the console logs
    }

    onRegularListChange(item: CustomListItem): void {
        //print something to the console logs
    }
}

回答1:

Call detectChanges() before you query dynamically created elements. They are not created before change detection has been run.

function createComponent(tcb: TestComponentBuilder): Promise<ComponentFixture> {
    return tcb.createAsync(CustomListComponentDemo).then((fixture) => {
        fixture.detectChanges();
        el = fixture.debugElement.nativeElement;
        regularListEl = fixture.debugElement.query(By.css(".list-unstyled"));
        dropdownListEl = fixture.debugElement.query(By.css(".dropdown-menu")).nativeElement;
        dropdownToggleBtn = fixture.debugElement.query(By.css(".btn")).nativeElement;

        return fixture;
    })
}