使用对服务的去抖动更新值调用测试表单字段
Testing a form field with a debounced updated value call to a service
我在一个组件中有一个表单域(用户名),当它更新时会调用一个服务来检查该值在数据库中的可用性。一切正常,但我似乎无法触发该字段的更新功能。
以下是组件代码的摘录:
ngAfterViewInit() {
this.username.update
.debounceTime(500) // wait 1/2 second before emitting last event
.distinctUntilChanged() // only emit value if different from last value
.subscribe((username: string) => {
console.log('IN SUBSCRIBE'); // <==== This is not being reached in the tests
let data = { type: 'username', val: username };
this._loginSrvc.chkUser(data).subscribe((response: IChkUserResponse)=>{
if(response.isAvailable === false)
this.username.control.setErrors({'taken': {value: this.username.value}});
});
});
}
组件模板中的字段如下:
<mat-form-field fxFlex>
<input matInput placeholder="Username" aria-label="Username" forbiddenCharacters=';"\\/\[\]\{\}\(\)' required [(ngModel)]="model.username" #username="ngModel" id="username" name="username">
<mat-error *ngIf="newUserFrm.hasError('required','username')">Username is required</mat-error>
<mat-error *ngIf="newUserFrm.hasError('forbiddenCharacters','username')">{{forbiddenChars.error}}</mat-error>
<mat-error *ngIf="newUserFrm.hasError('taken','username')">The username <em>{{model.username}}</em> is taken</mat-error>
</mat-form-field>
这是我的测试:
it('should check for username availability',fakeAsync(()=>{
spyOn(service,'chkUser');
let input = fixture.debugElement.query(By.css('#username')).nativeElement;
expect(input.value).toBe('');
expect(component.username.value).toBe(null);
input.value = user.username;
input.dispatchEvent(new Event('input'));
tick(500);
expect(service.chkUser).toHaveBeenCalled();
}));
最后一个 expect(service.chkUser).toHaveBeenCalled()
是导致我的测试失败的原因。我在一个应用程序中 运行 这个组件,它按预期工作,我只是想让测试通过。我尝试了很多组合,包括设置输入值和分派事件、等待装置检测变化或使用 whenStable()、投入承诺、使用 fakeAsync 而不是使用 fakeAsync。似乎没有任何效果。
所以我在经历了太多的挫折之后终于找到了我的问题。感谢那些发表评论的人让我朝着正确的方向思考,所以谢谢@jonrsharpe 和@BorisLobanov。
很明显,更新 EventEmitter 没有触发,input.dispatchEvent(new Event('input'));
也没有触发。这是第一个线索。我必须确保事件触发才能调用服务。
为了做到这一点,我已经从 TestBed 中获取组件的“componentInstance”。
beforeEach(() => {
fixture = TestBed.createComponent(NewUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}
为了触发事件,我需要访问该字段的底层 ngModel 获取其更新 EventEmitter 并发出一些东西。
所以在我的测试中我添加了这一行:
component.username.update.emit(null);
这让我的字段 运行 附加到事件的去抖动订阅函数。
但这并没有解决整个问题,运行测试给了我无法在未定义上执行 subscribe
。导致错误的订阅是附加到服务调用 this._loginSrvc.chkUser(data).subscribe((...)=>{...})
的订阅。
经过数小时摆弄 Observables 并重新创建我的模拟服务后,我终于在另一个问题中偶然发现了一个 Whosebug 答案,该问题说明在监视服务方法时要记住添加 .and.callThrough()
很简单。我的天啊!我怎么会忘记这一点(用手拍打额头)。订阅没有运行,因为 spyOn(service,'chkUser')
正在停止实际方法的执行,所以也没有可订阅的 Observable。在我看到的每个例子中,他们都试图做类似的事情,他们从来没有将 .and.callThrough()
添加到他们的间谍中,所以我从来没有想过它,即使我以前用过无数次。
无论如何,这是工作测试:
it('should check for username availability',fakeAsync(()=>{
spyOn(service,'chkUser').and.callThrough();
let input = fixture.debugElement.query(By.css('#username')).nativeElement;
expect(input.value).toBe('');
expect(component.username.value).toBe(null);
input.value = user.username;
component.username.update.emit(null);
tick(500);
expect(service.chkUser).toHaveBeenCalled();
}));
我在一个组件中有一个表单域(用户名),当它更新时会调用一个服务来检查该值在数据库中的可用性。一切正常,但我似乎无法触发该字段的更新功能。
以下是组件代码的摘录:
ngAfterViewInit() {
this.username.update
.debounceTime(500) // wait 1/2 second before emitting last event
.distinctUntilChanged() // only emit value if different from last value
.subscribe((username: string) => {
console.log('IN SUBSCRIBE'); // <==== This is not being reached in the tests
let data = { type: 'username', val: username };
this._loginSrvc.chkUser(data).subscribe((response: IChkUserResponse)=>{
if(response.isAvailable === false)
this.username.control.setErrors({'taken': {value: this.username.value}});
});
});
}
组件模板中的字段如下:
<mat-form-field fxFlex>
<input matInput placeholder="Username" aria-label="Username" forbiddenCharacters=';"\\/\[\]\{\}\(\)' required [(ngModel)]="model.username" #username="ngModel" id="username" name="username">
<mat-error *ngIf="newUserFrm.hasError('required','username')">Username is required</mat-error>
<mat-error *ngIf="newUserFrm.hasError('forbiddenCharacters','username')">{{forbiddenChars.error}}</mat-error>
<mat-error *ngIf="newUserFrm.hasError('taken','username')">The username <em>{{model.username}}</em> is taken</mat-error>
</mat-form-field>
这是我的测试:
it('should check for username availability',fakeAsync(()=>{
spyOn(service,'chkUser');
let input = fixture.debugElement.query(By.css('#username')).nativeElement;
expect(input.value).toBe('');
expect(component.username.value).toBe(null);
input.value = user.username;
input.dispatchEvent(new Event('input'));
tick(500);
expect(service.chkUser).toHaveBeenCalled();
}));
最后一个 expect(service.chkUser).toHaveBeenCalled()
是导致我的测试失败的原因。我在一个应用程序中 运行 这个组件,它按预期工作,我只是想让测试通过。我尝试了很多组合,包括设置输入值和分派事件、等待装置检测变化或使用 whenStable()、投入承诺、使用 fakeAsync 而不是使用 fakeAsync。似乎没有任何效果。
所以我在经历了太多的挫折之后终于找到了我的问题。感谢那些发表评论的人让我朝着正确的方向思考,所以谢谢@jonrsharpe 和@BorisLobanov。
很明显,更新 EventEmitter 没有触发,input.dispatchEvent(new Event('input'));
也没有触发。这是第一个线索。我必须确保事件触发才能调用服务。
为了做到这一点,我已经从 TestBed 中获取组件的“componentInstance”。
beforeEach(() => {
fixture = TestBed.createComponent(NewUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}
为了触发事件,我需要访问该字段的底层 ngModel 获取其更新 EventEmitter 并发出一些东西。
所以在我的测试中我添加了这一行:
component.username.update.emit(null);
这让我的字段 运行 附加到事件的去抖动订阅函数。
但这并没有解决整个问题,运行测试给了我无法在未定义上执行 subscribe
。导致错误的订阅是附加到服务调用 this._loginSrvc.chkUser(data).subscribe((...)=>{...})
的订阅。
经过数小时摆弄 Observables 并重新创建我的模拟服务后,我终于在另一个问题中偶然发现了一个 Whosebug 答案,该问题说明在监视服务方法时要记住添加 .and.callThrough()
很简单。我的天啊!我怎么会忘记这一点(用手拍打额头)。订阅没有运行,因为 spyOn(service,'chkUser')
正在停止实际方法的执行,所以也没有可订阅的 Observable。在我看到的每个例子中,他们都试图做类似的事情,他们从来没有将 .and.callThrough()
添加到他们的间谍中,所以我从来没有想过它,即使我以前用过无数次。
无论如何,这是工作测试:
it('should check for username availability',fakeAsync(()=>{
spyOn(service,'chkUser').and.callThrough();
let input = fixture.debugElement.query(By.css('#username')).nativeElement;
expect(input.value).toBe('');
expect(component.username.value).toBe(null);
input.value = user.username;
component.username.update.emit(null);
tick(500);
expect(service.chkUser).toHaveBeenCalled();
}));