【问题标题】:Angular - test displayed error on form validationAngular - 测试表单验证时显示的错误
【发布时间】:2020-05-11 00:02:51
【问题描述】:

我有一个看起来像这样的测试:

...
component.registerForm.controls['username'].setValue('VALID USERNAME');

fixture.detectChanges();

expect(component.registerForm.valid).toEqual(true);  // FIRST ASSERTION
const errors = fixture.debugElement.queryAll(By.css('.error.username'));
expect(errors.length).toBe(0);  // <-- SECOND ASSERTION: IT VAILS HERE!!!

问题在于,即使表单有效并且第一个断言通过,第二个断言也会失败,因为显示“必需”错误消息。对我来说,看起来好像表单已更新,而 fixture.debugElement 没有更新,因此它显示了初始错误。 IE。它忽略设置username 值。

更新: 我有非常相似的问题,并且相信来源可能是相同的:我有一些测试用例我想确保我的表单验证设置正确(即有效用例导致有效表单,无效用例导致无效表单)(我将表单简化为仅username):

getUsernameCases().forEach((testCase) => {
  it(`should be valid: ${testCase.valid}`, () => {
    component.myForm.get('username').setValue(testCase.username);

    component.registerForm.updateValueAndValidity();
    fixture.detectChanges();
    component.registerForm.updateValueAndValidity(); // Second time to make sure order does not matter

    expect(component.myForm.valid).toBe(testCase.valid); // ALWAYS INVALID HERE
  });
});

问题是无论值如何,表单总是无效的。它有required 错误 - 这意味着它显示了表单初始状态。在setValue之前。

【问题讨论】:

  • 我实际上会退后一步,问问这个测试的目的是什么。看起来你在测试 Angular 比在测试你自己的逻辑更多。充其量您正在测试此表单上的验证是否设置正确。
  • @joshrathke 我不同意你的看法。这只是一个简单的例子,但我想测试的是:1)我想看到我认为有效的用户名不会导致表单验证错误 2)我想看到无效(以某种方式)用户名导致适当的错误消息(这里我有.error.username,但对于缺失值,我可能有类似.error.username.required
  • 查看您正在测试的代码会很有帮助。一个最小的可重现示例。
  • 其次是你正在测试的代码,也许还有更多的实际测试代码。您如何构建夹具等等。
  • 能否也添加 HTML 代码?堆叠闪电战会很棒。请提供详细代码,因为代码的另一部分可能是您的问题的原因。

标签: angular angular-test


【解决方案1】:

这在很大程度上取决于您构建表单的方式,但我可以编写一个测试,以我认为您正在尝试的方式断言表单的有效性。我需要查看被测组件才能确定。

Here is a stackblitz showing these tests.

app.component.ts:

export class AppComponent implements OnInit {

  myForm: FormGroup;

  //Helper to get the username control to check error state
  get username() {
    return this.myForm.get('username');
  }

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    //"Username" has two validations to test: required and minLength
    this.myForm = this.fb.group({
      'username': new FormControl(null, [Validators.required, Validators.minLength(10)])
    });
  }
}

app.component.html:

<form [formGroup]="myForm">
  <!-- "username" input with conditional styles based on error state -->
  <input type="text" 
         formControlName="username"
         [class.username-valid]="!username.errors"
         [class.error-username-required]="username.errors?.required"
         [class.error-username-minlength]="username.errors?.minlength">
</form>

app.component.spec.ts:

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        FormsModule,
        ReactiveFormsModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

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

  describe('The form validity', () => {
    const testCases = [
      {
        expected: true,
        username: 'VALID_USERNAME',
        expectedCss: 'username-valid'
      },
      {
        expected: false,
        username: null,
        expectedCss: 'error-username-required'
      },
      {
        expected: false,
        username: 'too_short',
        expectedCss: 'error-username-minlength'
      }
    ];

    testCases.forEach(testCase => {
      it(`should update the form validity: ${testCase.expected}`, () => {
        component.myForm.get('username').setValue(testCase.username);
        component.myForm.updateValueAndValidity();
        fixture.detectChanges();
        expect(component.myForm.valid).toBe(testCase.expected); //assert the form is valid/invalid
        expect(fixture.debugElement.query(By.css(`.${testCase.expectedCss}`))).toBeTruthy(); //assert the conditional style is applied
      });
    });
  });
});

【讨论】:

    【解决方案2】:

    我要求您为您的问题提供 HTML 代码。以编程方式更改值不会将脏属性标记为真或假(我假设这是您的问题的原因)。您可以通过多种方式解决它:

    1. 我建议您更改 DOM 元素的值,而不是通过编程方式更改它。

      let inputEl = fixture.debugElement.queryAll(By.css('.inputElClass'));
      let inputElement = inputEl.nativeElement;
      
          //set input value
      inputElement.value = 'test value';
      inputElement.dispatchEvent(new Event('input'));
      
      fixture.detectChanges();
      
      expect(component.registerForm.valid).toEqual(true);  // FIRST ASSERTION
      const errors = fixture.debugElement.queryAll(By.css('.error.username'));
      expect(errors.length).toBe(0);  
      
    2. 您需要使用 FormControl API 方法(markAsDirty、markAsDirty),如下所示。

      const formcontrol = component.registerForm.controls['username']
      formcontrol.setValue('VALID USERNAME');
      this.formName.markAsDirty()
      this.formName.markAsTouched()
      

    【讨论】:

      【解决方案3】:

      几天前我遇到了类似的问题。 我通过调用解决了它

      component.myForm.get('username').updateValueAndValidity({ emitEvent: true })
      

      而不是component.myForm.updateValueAndValidity();

      基本上您需要在FormControl 级别触发值的重新计算和验证,而不是在FormGroup 级别。

      您可能不需要emitEvent 参数,在我的情况下,我订阅了FormGroup 以了解表单的状态是否有效,通过提供{emitEvent: true} Angular 将发出statusChanges 和@ 987654328@ 订阅将完成他们的工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-03-05
        • 1970-01-01
        • 2019-12-10
        • 1970-01-01
        • 2019-12-18
        • 2018-07-22
        • 2019-02-06
        相关资源
        最近更新 更多