简体   繁体   中英

Angular Reactive Form Unit Testing: HTML value and control value out of sync

I am following the Angular reactive form unit testing guide here but am perpetually unable to get the control value and the HTML value to synchronize. Below is my implementation; note that I am trying to call setValue in addition to specifying default values:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
    selector: 'user',
    templateUrl: './user.component.html',
    styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {

    loginForm!: FormGroup;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit(): void {
        this.initLoginForm();

        const usernameControl = this.loginForm.get('username');
        usernameControl?.valueChanges.subscribe(username => {
            debugger; // This doesn't fire
            this.loginForm.patchValue({username: username});
        });
    }

    initLoginForm() {
        this.loginForm = this.formBuilder.group({
            username: ['jack', Validators.compose([Validators.required])],
            password: ['JacksPassword']
        });

        this.loginForm.setValue({
            username: 'jack',
            password: 'JacksPassword'
        })

        this.loginForm.updateValueAndValidity();
    }
}
<form [formGroup]="loginForm" id="loginForm">
    <div>
        <input formControlName="username" placeholder="Username" required id="usernameInput"/>
    </div>
    <div>
        <input formControlName="password" placeholder="Password" type="password"/>
    </div>
</form>

And here are my tests:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder } from '@angular/forms';
import { UserComponent } from './user.component';

describe('UserComponent', () => {
    let component: UserComponent;
    let fixture: ComponentFixture<UserComponent>;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [UserComponent],
            providers: [FormBuilder]
        })
            .compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(UserComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should have same values between control and UI', () => {
        const loginFormUserElement: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#loginForm').querySelector('#usernameInput');
        const userNameValueFromGroup = component.loginForm.get('username');
        expect(loginFormUserElement.value).toEqual(userNameValueFromGroup?.value);
    });

    it('should accept username', () => {
        const loginFormUserElement: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#loginForm').querySelector('#usernameInput');
        loginFormUserElement.value = 'joe';
        loginFormUserElement.dispatchEvent(new Event('input'));
        fixture.detectChanges();
        fixture.whenStable().then(() => {
            const userNameValueFromGroup = component.loginForm.get('username');
            expect(loginFormUserElement.value).toEqual('joe');
            expect(loginFormUserElement.value).toEqual(userNameValueFromGroup?.value);
        });
    });
});

And here are the results. Setting defaults doesn't update the UI. Calling setValue doesn't update the UI. And setting a value on the UI element doesn't update the control.

在此处输入图像描述

It must be something basic. Whatever am I missing?

EDIT : I attempted to set up a StackBlitz implementation based on an isolated unit testing example, but the formGroup directive doesn't seem to be recognized under Jasmine; I am importing ReactiveFormsModule into app.module. The link is here in case anyone can provide insight about what I'm missing on this front.

EDIT : The problem here seems actually seems related to my unit testing effort. When I use the component directly, default values are displayed on the HTML input element correctly; however, when inspecting messages via the console on Chrome running Karma, I see the same error message indicating that the formGroup directive isn't recognized. I've updated the title accordingly.

The issue actually was purely in the setup of the unit test, binding between the control and the form worked fine when the component was used live. The problem in the unit test was that ReactiveFormsModule must be imported via TestBed.configureTestingModule ; I also was not calling ngOnInit during setup, although its absence doesn't seem to have any effect (yet).

My updated setup code is below. Curiously, attempting to import ReactiveFormsModule the same way in the StackBlitz setup produces an error 'Maximum call stack size exceeded', but it works fine on my machine.

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TimeRangeSelectorComponent],
            imports: [ReactiveFormsModule], // This is new
            providers: [
                FormBuilder,
                { provide: TimeRangeSelectorDefaultsBase, useValue: new TestTimeRangeSelectorDefaults() },
            ]
        });
        fixture = TestBed.createComponent(TimeRangeSelectorComponent);
        component = fixture.componentInstance;
        component.ngOnInit(); // This is new
        fixture.detectChanges();
    });

Turns out this is a dupe . Keeping this issue here in case it helps someone else; I didn't discover the other until I started searching for test-related issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM