Angular 单元测试表单验证

Posted

技术标签:

【中文标题】Angular 单元测试表单验证【英文标题】:Angular unit testing form validation 【发布时间】:2019-12-18 19:16:42 【问题描述】:

我正在学习 Angular 8,并且正在使用 Karma 进行单元测试。我创建了一个可以正常工作的基本注册表单,但我在测试中遇到了问题。

在测试时我遇到了 2 次失败

RegisterComponent > 表单应该是有效的 错误:预期验证器返回 Promise 或 Observable。

RegisterComponent > 应该调用 onSubmit 方法 错误: : 预期是间谍,但得到了 FormGroup( 验证器:函数,asyncValidator:null,_onCollectionChange:函数,原始:真,触摸:假,_onDisabledChange:[],控件:对象(名称:FormControl(验证器:函数,asyncValidator:null,_onCollectionChange:函数,原始:true,touched:false,_onDisabledChange:[函数],_onChange:[函数],_pendingValue:'',值:'',状态:'INVALID',错误:对象( required: true ), valueChanges: EventEmitter( _isScalar: false, 观察者: [ ], closed: false, isStopped: false, hasError: false, throwedError: null, __isAsync: false ), statusChanges: EventEmitter( _isScalar: false , 观察者: [ ], 关闭: false, isStopped: false, hasError: false, throwError: null, __isAsync: false ), _parent: ), 电子邮件: FormControl( validator: Function, asyncValidator: Function, _onCollectionChange: Function,原始:真,触摸:假,_onDisabledChange:[功能],_onChange:[功能],_pendingValue:'',值e:'' .... 用法:expect().toHaveBeenCalledTimes()

register.component.ts

import  ActivatedRoute, Router  from '@angular/router';
import  Component, OnInit  from '@angular/core';
import  FormBuilder, FormGroup, Validators  from '@angular/forms';

import  AuthenticationService  from '@/_services';
import  MustMatch  from '@/_helpers/validators';

@Component(
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
)
@Component( templateUrl: 'register.component.html' )
export class RegisterComponent implements OnInit 
  registerForm: FormGroup;
  submitted = false;
  returnUrl: string;
  error = '';

  constructor(
    private formBuilder: FormBuilder,
    private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService
  ) 
    if (this.authenticationService.currentUserValue) 
      this.router.navigate(['/']);
    
  

  ngOnInit() 
    this.registerForm = this.formBuilder.group(
      name: ['', Validators.required],
      email: ['', Validators.required, Validators.email],
      phone: ['', Validators.required],
      password: ['', Validators.required, Validators.minLength(6)],
      confirmPassword: ['', Validators.required],
    , 
        validator: MustMatch('password', 'confirmPassword')
      );

    this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/';
  

  get f() 
    return this.registerForm.controls;
  

  onSubmit() 
    this.submitted = true;
    if (this.registerForm.invalid) 
      return;
    

    this.submitted = false;
  

register.component.spec.ts

import  async, ComponentFixture, TestBed  from '@angular/core/testing';
import  By  from '@angular/platform-browser';
import  ReactiveFormsModule, FormsModule  from '@angular/forms';
import  RegisterComponent  from './register.component';
import  ActivatedRoute, Router  from '@angular/router';
import  HttpClientTestingModule  from '@angular/common/http/testing';
import  DebugElement  from '@angular/core';

describe('RegisterComponent', () => 
  let component: RegisterComponent;
  let fixture: ComponentFixture<RegisterComponent>;
  let de: DebugElement;
  let el: HTMLElement;
  const fakeActivatedRoute = 
    snapshot: 
      queryParams: 
        returnUrl: '/'
      
    
  ;
  const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

  beforeEach(async(() => 
    TestBed.configureTestingModule(
      imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
      declarations: [RegisterComponent],
      providers: [
         provide: Router, useValue: routerSpy ,
         provide: ActivatedRoute, useFactory: () => fakeActivatedRoute 
      ]

    ).compileComponents().then(() => 
      fixture = TestBed.createComponent(RegisterComponent);
      component = fixture.componentInstance;
      component.ngOnInit();
      fixture.detectChanges();
      de = fixture.debugElement.query(By.css('form'));
      el = de.nativeElement;
    );
  ));

  beforeEach(() => 
    fixture = TestBed.createComponent(RegisterComponent);
    component = fixture.componentInstance;
    component.ngOnInit();
    fixture.detectChanges();
  );

  it('form invalid when empty', () => 
    component.registerForm.controls.name.setValue('');
    component.registerForm.controls.email.setValue('');
    component.registerForm.controls.phone.setValue('');
    component.registerForm.controls.password.setValue('');
    component.registerForm.controls.confirmPassword.setValue('');
    expect(component.registerForm.valid).toBeFalsy();
  );

  it('username field validity', () => 
    const name = component.registerForm.controls.name;
    expect(name.valid).toBeFalsy();

    name.setValue('');
    expect(name.hasError('required')).toBeTruthy();

  );

  it('email field validity', () => 
    const email = component.registerForm.controls.email;
    expect(email.valid).toBeFalsy();

    email.setValue('');
    expect(email.hasError('required')).toBeTruthy();
  );

  it('phone field validity', () => 
    const phone = component.registerForm.controls.phone;
    expect(phone.valid).toBeFalsy();

    phone.setValue('');
    expect(phone.hasError('required')).toBeTruthy();

  );

  it('password field validity', () => 
    const password = component.registerForm.controls.password;
    expect(password.valid).toBeFalsy();

    password.setValue('');
    expect(password.hasError('required')).toBeTruthy();

  );

  it('confirmPassword field validity', () => 
    const confirmPassword = component.registerForm.controls.confirmPassword;
    expect(confirmPassword.valid).toBeFalsy();

    confirmPassword.setValue('');
    expect(confirmPassword.hasError('required')).toBeTruthy();

  );

  it('should set submitted to true', () => 
    component.onSubmit();
    expect(component.submitted).toBeTruthy();
  );

  it('should call onSubmit method', () => 
    spyOn(component, 'onSubmit');
    el = fixture.debugElement.query(By.css('button')).nativeElement;
    el.click();
    expect(component.registerForm).toHaveBeenCalledTimes(1);
  );

  it('form should be valid', () => 
    component.registerForm.controls.name.setValue('sasd');
    component.registerForm.controls.email.setValue('sadasd@asd.com');
    component.registerForm.controls.phone.setValue('132456789');
    component.registerForm.controls.password.setValue('qwerty');
    component.registerForm.controls.confirmPassword.setValue('qwerty');
    expect(component.registerForm.valid).toBeTruthy();
  );
);

我似乎无法理解导致此问题的原因。已经为此阅读了几个文档和教程,但它似乎不起作用。

【问题讨论】:

【参考方案1】:

Error: Expected validator to return Promise or Observable.

这意味着您错误地添加了多个验证器。 而不是这个:

this.registerForm = this.formBuilder.group(
          name: ['', Validators.required],
          email: ['', Validators.required, Validators.email],
          phone: ['', Validators.required],
          password: ['', Validators.required, Validators.minLength(6)],
          confirmPassword: ['', Validators.required],
        

试试这个:

this.registerForm = this.formBuilder.group(
          name: ['', Validators.required],
          email: ['', [Validators.required, Validators.email]],
          phone: ['', Validators.required],
          password: ['', [Validators.required, Validators.minLength(6)]],
          confirmPassword: ['', Validators.required],
        

注意如何在一个数组中提供多个验证器,而不仅仅是逗号分隔。

对于你的第二个错误,你想调用

expect(component.onSubmit).toHaveBeenCalledTimes(1);

代替

expect(component.registerForm).toHaveBeenCalledTimes(1);

【讨论】:

【参考方案2】:
expect(component.onSubmit).toHaveBeenCalledTimes(1)

在下面的代码中替换上面的行,因为你想检查 onSubmit 方法而不是 component.registerForm

it('should call onSubmit method', () => 
        spyOn(component, 'onSubmit');
        el = fixture.debugElement.query(By.css('button')).nativeElement;
        el.click();
        expect(component.registerForm).toHaveBeenCalledTimes(1);
    );

【讨论】:

以上是关于Angular 单元测试表单验证的主要内容,如果未能解决你的问题,请参考以下文章

ag-grid-angular 和单元格验证

怎么消除angular的表单验证

angular表单怎么验证输入最大小长度

Angular 表单验证和引导样式

Angular 4 表单验证和错误消息

Angular 2 提交时触发表单验证