测试包含自定义表单控件的角度组件?

时间:2018-09-05 05:36:00

标签: angular unit-testing karma-runner

我有一个简单的角度分量,如下所示。

import { Component, Input, forwardRef, ViewEncapsulation } from "@angular/core";
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from "@angular/forms";

export const CUSTOM_CHECKBOX_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CheckBoxComponent),
    multi: true
};

@Component({
    selector: "checkbox",
    templateUrl: "./checkBox.component.html",
    styleUrls: ["./checkBox.component.scss"],
    providers: [CUSTOM_CHECKBOX_CONTROL_VALUE_ACCESSOR],
    encapsulation: ViewEncapsulation.None
})
export class CheckBoxComponent implements ControlValueAccessor {
    @Input() name: string;
    // Placeholders for the callbacks which are later provided
    // by the Control Value Accessor
    private innerValue: any = "";
    private onTouchedCallback: () => void = () => { };
    private onChangeCallback: (_: any) => void = () => { };

    // get and set accessor----------------------
    get value(): any {
        return this.innerValue;
    }
    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    // From ControlValueAccessor interfaces--------------
    writeValue(value: any): void {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }
    registerOnChange(fn: any): void { this.onChangeCallback = fn; }
    registerOnTouched(fn: any): void { this.onTouchedCallback = fn; }
}

我正在为此编写单元测试。我的测试如下所示

    import { CheckBoxComponent, CUSTOM_CHECKBOX_CONTROL_VALUE_ACCESSOR } from "./checkBox.component";
        import { TestBed, async, ComponentFixture, fakeAsync, tick } from "@angular/core/testing";
        import { DebugElement, Component } from "@angular/core";
        import { By } from "@angular/platform-browser";
        import { FormsModule } from "@angular/forms";
        import { CommonModule } from "@angular/common";

        @Component({
            selector: "tac-checkbox",
            template: `<checkbox name="checkboxId1" label="Checkbox Label" [(ngModel)] = "checkValue" > `,
        })
        class CustomTestClass {
            checkValue = false;
        }

        describe("Component: CheckBoxComponent", () => {
            let component: CheckBoxComponent;
            let fixture: ComponentFixture<CheckBoxComponent>;
            let testComponent: CustomTestClass;
            let testFixture: ComponentFixture<CustomTestClass>;

            beforeEach(async(() => {
                TestBed.configureTestingModule({
                    imports: [FormsModule, CommonModule],
                    declarations: [CheckBoxComponent, CustomTestClass],
                    providers: [CUSTOM_CHECKBOX_CONTROL_VALUE_ACCESSOR],
                }).compileComponents().then(() => {
                    fixture = TestBed.createComponent(CheckBoxComponent);
                    component = fixture.componentInstance;
                    component.name = "checkbox";
                });
            }));

            it("...", fakeAsync(() => {
                testFixture = TestBed.createComponent(CustomTestClass);
                testComponent = testFixture.componentInstance;

                fixture.detectChanges();
                const onChangeEvent = (change: any) => { console.log("There were changes ", change); };
                const registerOnChangeMock = spyOn(component, "registerOnChange").and.callThrough();
                const registerOnTouchedMock = spyOn(component, "registerOnTouched").and.callThrough();
                const onMockWriteValue = spyOn(component, "writeValue").and.callThrough();

                component.registerOnChange(onChangeEvent);

                component.registerOnTouched(onChangeEvent);

                testComponent.checkValue = !testComponent.checkValue;
                fixture.detectChanges();
                testFixture.detectChanges();
                tick();
                fixture.whenStable().then(() => {
                    expect(registerOnChangeMock).toHaveBeenCalledTimes(1); //pass
                    expect(registerOnTouchedMock).toHaveBeenCalledTimes(1);
 //pass
                    expect(onMockWriteValue).toHaveBeenCalled(); //failed
                    expect(testComponent.checkValue).toEqual(component.value); //failed
                });
            }));

        });

根据上面的测试,我希望component.value等于testComponent.value。但是,这失败了,我的问题是考虑到我更改了testComponent值,是否也应该更新控件值?另外,我期望每当更改testComponent值时都将调用writeValue,但是事实并非如此。请问我做错了什么?任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:2)

这是因为component与您期望的实例不同。在beforeEach中,您将创建新的CheckBoxComponent;在it中,您将创建测试主机组件CustomTestClass的实例。 Angular创建新的CheckBoxComponent作为测试主机组件的子代,因此,当您更改主机组件的输入值时,它不会反映在component实例中。

您应该做的只是创建测试主机组件,然后在该组件中注入CheckBoxComponent@ViewChild并在测试中使用该实例。

代码:

删除.then中的beforeEach部分。

编辑测试主机组件:

class CustomTestClass {
   checkValue = false;
   @ViewChild(CheckBoxComponent) checkBoxComponent: CheckBoxComponent;
}

在测试中将component替换为testComponent.checkBoxComponent