Angular 角业茉莉花测试表验证错误

Angular 角业茉莉花测试表验证错误,angular,karma-jasmine,Angular,Karma Jasmine,我希望进行测试,以确保在构建时对表单控件进行的验证得到应用。这可能是因为我在检查错误之前设置了表单控件值吗?表单是在专用于整个表单的服务中构建的。这是一份6页的报名表。我想确保我构建表单时使用的验证器正在我的karma jasmine测试中应用。申请人是通过一个对后端进行http调用的服务和另一个构建表单的服务在NIT上定义的。我创建了一个通用的注册表单模拟,以表示从规范中的后端调用返回的数据。理想情况下,我需要工作的是期望所需的验证器和验证器模式实际上是在构建时传递到表单中的: (请参阅本文底

我希望进行测试,以确保在构建时对表单控件进行的验证得到应用。这可能是因为我在检查错误之前设置了表单控件值吗?表单是在专用于整个表单的服务中构建的。这是一份6页的报名表。我想确保我构建表单时使用的验证器正在我的karma jasmine测试中应用。申请人是通过一个对后端进行http调用的服务和另一个构建表单的服务在NIT上定义的。我创建了一个通用的注册表单模拟,以表示从规范中的后端调用返回的数据。理想情况下,我需要工作的是期望所需的验证器和验证器模式实际上是在构建时传递到表单中的:

(请参阅本文底部的错误照片)

正在向此表单控件传递生成时需要的Validators.required。然而,在测试期间,验证器错误显示为未定义

表单生成器:

phone_number: [null, Validators.required],
<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>
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;
  }
表单验证规范:

    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();
  });
组件HTML:

phone_number: [null, Validators.required],
<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>
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;
  }

此问题看起来好像没有发生任何更改检测。当您直接更新内部值时,表单可能会有点奇怪。每当表单检测到发生了更改时,验证程序逻辑就会重新运行,这通常发生在直接使用字段时(想想blur/dirty)。但是,您设置值的方式可能会绕过这一点

确保验证逻辑运行并获得适当的错误响应/表单逻辑的一种方法是告诉表单控件它已过期

  component.yourForm.controls['yourFormField'].markAsTouched();
  fixture.detectChanges();
这将运行验证器,您将看到预期的行为

注意,您可能还希望尝试使用本机元素来更新表单,而不是转到组件本身。这确保测试检查有助于模拟用户体验的模板。如果您选择执行该路线,则可以使用此代码

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

这个问题看起来好像没有发生任何更改检测。当您直接更新内部值时,表单可能会有点奇怪。每当表单检测到发生了更改时,验证程序逻辑就会重新运行,这通常发生在直接使用字段时(想想blur/dirty)。但是,您设置值的方式可能会绕过这一点

确保验证逻辑运行并获得适当的错误响应/表单逻辑的一种方法是告诉表单控件它已过期

  component.yourForm.controls['yourFormField'].markAsTouched();
  fixture.detectChanges();
这将运行验证器,您将看到预期的行为

注意,您可能还希望尝试使用本机元素来更新表单,而不是转到组件本身。这确保测试检查有助于模拟用户体验的模板。如果您选择执行该路线,则可以使用此代码

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

对我有效的方法是调用
markAsTouched()
函数,并手动设置该控件的错误

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

希望答案对其他人有帮助。

对我有效的方法是调用
markAsTouched()
函数,并手动设置该控件的错误

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

希望答案能对其他人有所帮助。

从Angular materials第9版开始,组件线束随测试API一起提供。 要测试mat输入错误,您可以尝试
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 );
} );

从Angle materials的第9版开始,组件线束随测试API一起提供。 要测试mat输入错误,您可以尝试
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 );
} );

你能给我看一下组件的代码吗?问题会更清楚、更容易理解。@Halpenny您可以添加component.ts文件吗,它会更有用:)当您的申请者从服务返回控件时,我们也需要它。请尽量保持一致性:如果有未定义的内容,我们需要知道它的定义位置,不要逐个包含代码示例。我们仍然不知道申请人的定义是什么,因为我们现在需要了解更多关于enrollmentForm的信息。这在单元测试中是一个不同的故事。理想情况下,您应该模拟MaEnrollmentFormService。这部分代码不清楚。如果您不模拟-没有注册表单的定义。您能显示组件的代码吗?问题会更清楚、更容易理解。@Halpenny您可以添加component.ts文件吗,它会更有用:)当您的申请者从服务返回控件时,我们也需要它。请尽量保持一致性:如果有未定义的内容,我们需要知道它的定义位置,不要逐个包含代码示例。我们仍然不知道申请人的定义是什么,因为我们现在需要了解更多关于enrollmentForm的信息。这在单元测试中是一个不同的故事。理想情况下,您应该模拟MaEnrollmentFormService。这部分代码不清楚。如果您不模拟-没有注册表单的定义。