Testing angular component which contains a custom form control?

Viewed 4817

I have a simple angular component like below.

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

I am writing a unit test for. My test is like below

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

        });

From my test above, i expect component.value to equal testComponent.value. This failed however, My question is considering i changed the testComponent value, shouldn't the control value be updated as well? Also i was expecting the writeValue to be called whenever i changed the testComponent value, however, it wasn't. Please what am i doing wrong ? Any help would be appreciated.

1 Answers

It is because the component is not same instance as you are expecting. In beforeEach you are creating new CheckBoxComponent and in it you are creating instance of your test host component CustomTestClass. Angular creates new CheckBoxComponent as a child of your test host component, so when you change the input value for the host component it is not reflected in component instance.

What should you do is to create just the test host component and in that component inject the CheckBoxComponent with @ViewChild and use that instance in tests.

Code:

remove .then part in beforeEach.

edit your test host component:

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

replace component with testComponent.checkBoxComponent in your test.

Related