You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义异步验证器测试异常:表单控件invalid属性不符合预期

问题

尝试测试自定义异步验证器返回错误键值对时,表单控件是否会变为无效状态。

创建的Mock验证器代码:

export async function mockUniqueEmailValidatorFn(control: AbstractControl) {
    const existingEmailAddresses = ['TestEmail@gmail.com'];
    if(existingEmailAddresses.includes(control.value)) {
        return of({'emailUniquenessViolated': true})
            .pipe(
                first()
            );
    } else {
        return of(null);
    }
}

单元测试代码:

it('should be invalid when email has already been used to create an account', () => {
    //Arrange
    component.registerForm.controls['email'].clearAsyncValidators;
    component.registerForm.controls['email'].addAsyncValidators(mockUniqueEmailValidatorFn);
    //Act
    component.registerForm.controls['email'].setValue("TestEmail@gmail.com");
    fixture.detectChanges();
    console.log(component.registerForm.controls['email']);
    
    //Assert
    expect(component.registerForm.controls['email'].invalid).toBeTruthy();
});

测试失败,控件invalid属性为false,打印控件发现两个异常:

  1. 控件既非有效也非无效
  2. 控件状态为"PENDING",尽管已使用rxjs的first()操作符
原因与解决方案

1. Mock验证器的async关键字错误

Angular异步验证器要求返回Observable<any>或Promise<any>,但你给验证器函数加了async关键字,这会让函数返回Promise<Observable<any>>,不符合Angular的要求,直接导致验证流程异常。

修复: 去掉验证器函数的async关键字

export function mockUniqueEmailValidatorFn(control: AbstractControl) {
    const existingEmailAddresses = ['TestEmail@gmail.com'];
    if(existingEmailAddresses.includes(control.value)) {
        return of({'emailUniquenessViolated': true})
            .pipe(
                first()
            );
    } else {
        return of(null);
    }
}

2. 未正确执行清除验证器的方法

clearAsyncValidators是一个方法,需要加括号才能调用执行,否则只是引用方法本身,根本没有清除原有验证器。

修复: 改为调用方法

component.registerForm.controls['email'].clearAsyncValidators();

3. 未等待异步验证完成就执行断言

异步验证器的Observable是异步执行的,直接调用fixture.detectChanges()不会等待异步操作完成,此时控件状态还处于PENDING,自然无法得到正确的invalid值。

修复: 使用Angular测试工具的fakeAsync和tick()同步处理异步流程:

it('should be invalid when email has already been used to create an account', fakeAsync(() => {
    //Arrange
    component.registerForm.controls['email'].clearAsyncValidators();
    component.registerForm.controls['email'].addAsyncValidators(mockUniqueEmailValidatorFn);
    //Act
    component.registerForm.controls['email'].setValue("TestEmail@gmail.com");
    fixture.detectChanges();
    tick(); // 等待所有异步操作完成
    fixture.detectChanges(); // 更新控件状态
    //Assert
    expect(component.registerForm.controls['email'].invalid).toBeTruthy();
}));

或者使用async/await结合whenStable():

it('should be invalid when email has already been used to create an account', async () => {
    //Arrange
    component.registerForm.controls['email'].clearAsyncValidators();
    component.registerForm.controls['email'].addAsyncValidators(mockUniqueEmailValidatorFn);
    //Act
    component.registerForm.controls['email'].setValue("TestEmail@gmail.com");
    fixture.detectChanges();
    await fixture.whenStable(); // 等待异步操作稳定
    fixture.detectChanges();
    //Assert
    expect(component.registerForm.controls['email'].invalid).toBeTruthy();
});

内容的提问来源于stack exchange,提问作者Pablo52

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 00:35:15