自定义异步验证器测试异常:表单控件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,打印控件发现两个异常:
- 控件既非有效也非无效
- 控件状态为
"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
相关产品推荐
相关产品推荐

