likes
comments
collection
share

异步验证器单元测试的编写

作者站长头像
站长
· 阅读数 26

比如我们当前创建了一个如下的异步验证器,我们想要写一个单元测试来保证其准确性。

 vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return this.vehicleBrandService.existByName(control.value).pipe(map(exists => exists ? {vehicleBrandNameExist: true} : null));
    };
  }

我们再mockApi中设置了如果name为‘车辆品牌’则返回true,若为‘车辆’则返回false,其余情况随即返回。

if(name === '车辆品牌') {
  console.log('return true');
  return true;
  }
if(name === '车辆')
  return false;
return randomNumber(5) < 2;

起初想的是只要像这样在单元测试中创建一个formControl然后再设置它的值为'车辆品牌'然后再断定formControl.errors.vehicleBrandNameExist为true即可,但是系统会发生报错提示formControl.errors.vehicleBrandNameExist为undefined;之后又加入了setTimeout进行延时处理

 beforeEach(() => {
    asyncValidate = TestBed.inject(VehicleBrandAsyncValidator);
    service = TestBed.inject(VehicleBrandService);
  });
  fit('should create an instance', async () => {
    expect(asyncValidate).toBeTruthy();
    let formControl = new FormControl('', asyncValidate.vehicleBrandNameNotExist());
    formControl.setValue('车辆品牌');
    setTimeout(() => console.log(formControl.errors.vehicleBrandNameExist), 500);
  });

经过输出发现还是为undefined.之后我有分别在验证器C层,M层,和mockApi中进行输出相应的断点,发现根本没有执行到mockApi中,只执行到了M层。异步验证器单元测试的编写之后我又尝试在相应的模块的单元测试中进行如下测试,发现可以实现我们想要的效果。

  beforeEach(() => {
    fixture = TestBed.createComponent(AddComponent);
    asyncValidate = TestBed.inject(VehicleBrandAsyncValidator);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  fit('should create', () => {
    component.formGroup.get(component.formKeys.name).setValue('车辆品牌');
    setTimeout(() => console.log(component.formGroup.get(component.formKeys.name).errors.vehicleBrandNameExist), 100);
  });

异步验证器单元测试的编写这时问题就出现了,我们只是把formControl声明在了component中就可以实现我们想要的效果,formControl只是改变了获取方式——从直接创建获取变成了在component中创建,再通过component获取。为什么就会出现这样的结果呢。难道在组件中还会有什么特殊的机制?

最后询问老师之后才发现并没有什么特殊的机制,如果仔细查看两者的区别的话,我们会发现在组件中我们是这样对formControl声明的

this.formGroup.addControl(this.formKeys.name, new FormControl('', Validators.required, this.vehicleBrandAsyncValidator.vehicleBrandNameNotExist()));

而在单元测试中是这样:

let formControl = new FormControl('', asyncValidate.vehicleBrandNameNotExist());

写错了参数位置,第二个参数为同步验证器,第三个参数才是异步验证器。我们查看formControl的构造函数便可得知:

constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);

没想到竟然犯了这种错误,之前一直认为像是这种错误编辑器都会直接指出来,或是会发生相应报错,但是确实会出现像上面这种情况,所以我们在调用某些函数或者初始化对象时首先要明确它的参数是什么,不能只根据先前经验。

在此期间还可能遇到以下问题:

    let asyncValidator: VehicleBrandAsyncValidator;
    let formControl = new FormControl('', asyncValidator.vehicleBrandNameNotExist() );
    formControl.setValue('车辆品牌');

如果我们这样使用VehicleBrandAsyncValidator的话会发生报错,我们会发现asyncValidator为空,我们需要手动通过一下代码将VehicleBrandAsyncValidator注入进来。

 asyncValidate = TestBed.inject(VehicleBrandAsyncValidator);

我很还有可能在执行上述代码时可能什么都不会执行即不会触发验证器,那么很有可能是由于在

let formControl = new FormControl('', asyncValidator.vehicleBrandNameNotExist() );

中我们将其写为了

let formControl = new FormControl('', asyncValidator.vehicleBrandNameNotExist );

这种情况在C层编辑器会告诉我们类型不匹配,但是在单元测试中不会。

上面的用setTimeout加入延迟的方法完全可以用getTestScheduler().flush()方法取代进行手动返回数据,这是由于我误认为getTestScheduler().flush()在次单元测试中不适用造成的。