简体   繁体   中英

Angular Karma Jasmine Testing form validation error

I am wanting to test to ensure the validation I give to the form control at build time is applied. Could this be because I am setting the form control value prior to checking the error? The form is being built in a service dedicated to the overall form. It is a 6 page enrollment form. I want to be sure the validators I build the form with are being applied in my karma-jasmine test. Applicant is defined onInit with a service that makes an http call to the back-end and another service that builds the form. I have created a generic enrollment form mock to represent the data being returned from the back-end call in the spec. Ideally what I need to get working are expectations that the required validator and validator patterns are in fact what was passed into the form at build time:

(See error photo at bottom of this post)

expect(errors.required).toBeTruthy();
    expect(errors.pattern).toBeTruthy();

This form control is being passed a Validators.required at build. However, during test validator errors are being shown as undefined ...

Form Builder:

phone_number: [null, Validators.required],

Form Validation Spec:

    it('phone number field validity - required', () => {
    const phoneNumber = component.applicant.controls.phone_number;
    expect(phoneNumber.valid).toBeFalsy();

    phoneNumber.setValue(mockGenericMaEnrollmentRefresh.applicant.phone_number);
    const errors = phoneNumber.errors || {};
    // expect(errors.required).toBeTruthy();
    expect(errors.pattern).toBe(undefined);
    expect(phoneNumber.valid).toBeTruthy();
  });

Component HTML:

<mat-form-field>
            <mat-label>Phone *</mat-label>
            <input [textMask]="{mask: phoneMask}" formControlName="phone_number" id="phone_number" matInput
                   type="text">
            <mat-error *ngIf="applicant.controls.phone_number.errors?.required">Phone Number is
              required.
            </mat-error>
          </mat-form-field>

Component TS:

import {Component, OnInit} from '@angular/core';
import {GoToStepConfig, MaNavigationService} from '../../services/ma-navigation/ma-navigation.service';
import {MaEnrollmentFormService} from '../../services/ma-enrollment-form/ma-enrollment-form.service';
import {FormControl, FormGroup} from '@angular/forms';

@Component({
  selector: 'app-ma-personal-info',
  templateUrl: './ma-personal-info.component.html',
  styleUrls: ['./ma-personal-info.component.css']
})
export class MaPersonalInfoComponent implements OnInit {
  phoneMask: any[] = [/\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/];
  mailingAddressSameAsResidential = new FormControl(null);

  constructor(private maNavigationService: MaNavigationService,
              private formService: MaEnrollmentFormService) {
  }

  get enrollmentForm(): FormGroup {
    return this.formService.enrollmentForm;
  }

  get applicant(): FormGroup {
    return this.formService.applicant as FormGroup;
  }

  get permanentAddress(): FormGroup {
    return this.formService.permanentAddress as FormGroup;
  }

  get mailingAddress(): FormGroup {
    return this.formService.mailingAddress as FormGroup;
  }

  get medicaid(): FormGroup {
    return this.formService.medicaid as FormGroup;
  }

  get providerForm(): FormGroup {
    return this.formService.providerForm as FormGroup;
  }

  ngOnInit() {
  }

  isApplicantBasicInfoValid(): boolean {
    return this.applicant.controls.first_name.valid &&
      this.applicant.controls.last_name.valid &&
      this.applicant.controls.date_of_birth.valid &&
      this.applicant.controls.phone_number.valid &&
      this.applicant.controls.gender.valid;
  }

  isAddressValid(): boolean {
    return this.permanentAddress.valid && this.mailingAddress.valid;
  }

  isMedicareInformationValid(): boolean {
    return this.applicant.controls.medicare_claim_number.valid &&
      this.applicant.controls.hospital_insurance_parta.valid &&
      this.applicant.controls.medical_insurance_partb.valid &&
      this.enrollmentForm.controls.proposed_effective_date.valid;
  }

  isMedicaidInformationValid(): boolean {
    return this.providerForm.valid && this.medicaid.valid;
  }

  areAllControlsValid(): boolean {
    return this.applicant.valid && this.medicaid.valid && this.providerForm.valid &&
      this.enrollmentForm.controls.proposed_effective_date.valid;
  }

  back() {
    this.maNavigationService.returnToQuotePage();
  }

  next() {

    this.applicant.markAllAsTouched();
    this.medicaid.markAllAsTouched();
    this.permanentAddress.markAllAsTouched();
    this.mailingAddress.markAllAsTouched();
    this.providerForm.markAllAsTouched();

    if (!this.areAllControlsValid()) {
      return;
    }

    const goToStepConfig: GoToStepConfig = {
      route: '/ma/enroll/2'
    };
    this.maNavigationService.goToStep(goToStepConfig);
  }

}

///////////// PAGE 1 GETTERS FORM SERVICE ///////////////
  get applicant(): FormGroup {
    return this.enrollmentForm.get('applicant') as FormGroup;
  }

  get permanentAddress(): FormGroup {
    return this.applicant.get('permanent_address') as FormGroup;
  }

  get mailingAddress(): FormGroup {
    return this.applicant.get('mailing_address') as FormGroup;
  }

  get medicaid(): FormGroup {
    return this.enrollmentForm.get('medicaid') as FormGroup;
  }

  get providerForm(): FormGroup {
    return this.enrollmentForm.get('plan.primary_care_physician.provider') as FormGroup;
  }

表单验证错误

This issue looks to be like some change detection is not happening. Forms can be a little weird when you directly update the internal values. The validator logic is re-run whenever the form detects a change to have occurred, this usually happens when the field is directly used (think blur/dirty). However the way you have set the values might bypass this.

A way to ensure that the validation logic runs and you get the appropriate error response/form logic is to tell the form control that it is out of date.

  component.yourForm.controls['yourFormField'].markAsTouched();
  fixture.detectChanges();

This will run the validators and you will see the expected behavior.

As a note, you may also want to try using the native element to update the form rather than go to the component itself. This ensures that the test checks the template which helps simulate the user experience. If you choose to do that route you can use this code.

  yourFormInput = dom.query(By.css('[formcontrolname=yourFormField]')).nativeElement;
  yourFormInput.value = deviceName;
  yourFormInput.dispatchEvent(new Event('input'));
  fixture.detectChanges();

Something that worked for me is to call the markAsTouched() function and also set manually the error for that control.

component.yourForm.controls['yourFormField'].markAsTouched();
component.yourForm.controls['yourFormField'].setErrors({ required: true });
fixture.detectChanges();

Hope the answer will be helpful for others.

From version 9 of Angular materials, ComponentHarnesses are provided along with testing API. For testing mat input errors, you can try MatInputHarness .

it( 'should display validation errors when the input is focus and the value is invalid', async () => {
    const matFormFieldHarness = await loader.getHarness( MatFormFieldHarness );
    const inputHarness = await matFormFieldHarness .getHarness( MatInputHarness );

    await inputHarness.focus(); // focus in input
    await inputHarness.setValue( invalid_value ); // set value to input
    await inputHarness.blur(); // focus out input

    const errors = await matFormFieldHarness.getTextErrors(); // get an array of 
validation errors

    expect( errors ).toContain( error_message );
} );

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