为使用 mat-autocomplete 的组件编写单元测试
Writing unit test for component which uses mat-autocomplete
我是 Angular 的新手,我正在尝试使用 Angular 5.
构建一个具有自动完成功能的文本字段
我在 Angular Material docs 中找到了这个例子:
https://stackblitz.com/angular/kopqvokeddbq?file=app%2Fautocomplete-overview-example.ts
我想知道如何编写单元测试来测试自动完成功能。我正在为输入元素设置一个值并触发一个 'input' 事件并尝试选择 mat-option 元素,但看到创建了 none 个元素:
我组件的相关部分html:
<form>
<mat-form-field class="input-with-icon">
<div>
<i ngClass="jf jf-search jf-lg md-primary icon"></i>
<input #nameInput matInput class="input-field-with-icon" placeholder="Type name here"
type="search" [matAutocomplete]="auto" [formControl]="userFormControl" [value]="inputField">
</div>
</mat-form-field>
</form>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name"
(onSelectionChange)="onNameSelect(option)">
{{ option.name }}
</mat-option>
</mat-autocomplete>
规格文件:
it('should filter users based on input', fakeAsync(() => {
const hostElement = fixture.nativeElement;
sendInput('john').then(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelectorAll('mat-option').length).toBe(1);
expect(hostElement.textContent).toContain('John Rambo');
});
}));
function sendInput(text: string) {
let inputElement: HTMLInputElement;
inputElement = fixture.nativeElement.querySelector('input');
inputElement.focus();
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
组件html:
userFormControl: FormControl = new FormControl();
ngOnInit() {
this.filteredOptions = this.userFormControl.valueChanges
.pipe(
startWith(''),
map(val => this.filter(val))
);
}
filter(val: string): User[] {
if (val.length >= 3) {
console.log(' in filter');
return this.users.filter(user =>
user.name.toLowerCase().includes(val.toLowerCase()));
}
}
在此之前,我意识到要让FormControl对象设置值,我必须先做一个inputElement.focus(),这与使用angular material的垫输入有关。我必须做些什么才能触发打开 mat-options 窗格吗?
如何使这个测试起作用?
您需要添加更多活动。我或多或少遇到了与您相同的问题,并且仅在触发 focusin 事件时才起作用。
我在我的代码中使用了这些事件。不确定是否需要全部。
inputElement.dispatchEvent(new Event('focus'));
inputElement.dispatchEvent(new Event('focusin'));
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('keydown'));
您需要将此添加到您的 sendInput 函数中...
@Adam's comment to previous answer led me to the mat-autocomplete component's own test, specially here。您可以在哪里看到 focusin
是打开 "options".
的事件
但它们实际上在您的组件外部的覆盖层中打开,所以在我的测试中 fixture.nativeElement.querySelectorAll('mat-option').length
是 0
但是如果我查询元素 document.querySelectorAll('mat-option')
我得到了预期的选项数量.
总结一下:
fixture.detectChanges();
const inputElement = fixture.debugElement.query(By.css('input')); // Returns DebugElement
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
inputElement.nativeElement.value = text;
inputElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(3,
'Expect to have less options after input text and filter');
额外的球:如果你想点击一个选项(我点击了)你可以继续这样:
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
fixture.detectChanges();
虽然我没有成功点击并将值输入到输入中。嗯,我不是专家测试人员,但可能这种行为应该包含在自己的 mat-autocomplete
测试中(实际上是)并依赖它?
我在这里进一步建立@David 的回答。
提供正在测试的组件有@Output() selectedTimezone = new EventEmitter<string>();
,
并在组件模板中
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)">
,
捕获具有正确值的适当类型事件的单元测试如下
it('should emit selectedTimezone event on optionSelected', async() => {
// Note: 'selectedTimezone' is @Output event type as specified in component's signature
spyOn(component.selectedTimezone, 'emit');
const inputElement = fixture.debugElement.query(By.css('input'));
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
/**
* Note, mat-options in this case set up to have array of ['Africa/Accra (UTC
* +01:00)', 'Africa/Addis_Ababa (UTC +01:00)', 'Africa/Algiers (UTC +01:00)',
* 'Africa/Asmara (UTC +01:00)']. I am setting it up in 'beforeEach'
*/
inputElement.nativeElement.value = 'Africa';
inputElement.nativeElement.dispatchEvent(new Event('input'));
await fixture.whenStable();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(4);
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
// With this expect statement we verify both, proper type of event and value in it being emitted
expect(component.selectedTimezone.emit).toHaveBeenCalledWith('Africa/Accra');
});
感谢@David 的回答,我在选择选项部分之前一切正常。
为了使选项选择起作用,我必须这样做;
...
matOptions[0].dispatchEvent(new Event('click'));
...
而且您不必将 matOptions[0]
的类型转换为 HTMLElement
注意**我使用的是 Angular 8.0.1,新版本中的解决方案可能有所不同
我是 Angular 的新手,我正在尝试使用 Angular 5.
构建一个具有自动完成功能的文本字段我在 Angular Material docs 中找到了这个例子:
https://stackblitz.com/angular/kopqvokeddbq?file=app%2Fautocomplete-overview-example.ts
我想知道如何编写单元测试来测试自动完成功能。我正在为输入元素设置一个值并触发一个 'input' 事件并尝试选择 mat-option 元素,但看到创建了 none 个元素:
我组件的相关部分html:
<form>
<mat-form-field class="input-with-icon">
<div>
<i ngClass="jf jf-search jf-lg md-primary icon"></i>
<input #nameInput matInput class="input-field-with-icon" placeholder="Type name here"
type="search" [matAutocomplete]="auto" [formControl]="userFormControl" [value]="inputField">
</div>
</mat-form-field>
</form>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name"
(onSelectionChange)="onNameSelect(option)">
{{ option.name }}
</mat-option>
</mat-autocomplete>
规格文件:
it('should filter users based on input', fakeAsync(() => {
const hostElement = fixture.nativeElement;
sendInput('john').then(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelectorAll('mat-option').length).toBe(1);
expect(hostElement.textContent).toContain('John Rambo');
});
}));
function sendInput(text: string) {
let inputElement: HTMLInputElement;
inputElement = fixture.nativeElement.querySelector('input');
inputElement.focus();
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
组件html:
userFormControl: FormControl = new FormControl();
ngOnInit() {
this.filteredOptions = this.userFormControl.valueChanges
.pipe(
startWith(''),
map(val => this.filter(val))
);
}
filter(val: string): User[] {
if (val.length >= 3) {
console.log(' in filter');
return this.users.filter(user =>
user.name.toLowerCase().includes(val.toLowerCase()));
}
}
在此之前,我意识到要让FormControl对象设置值,我必须先做一个inputElement.focus(),这与使用angular material的垫输入有关。我必须做些什么才能触发打开 mat-options 窗格吗?
如何使这个测试起作用?
您需要添加更多活动。我或多或少遇到了与您相同的问题,并且仅在触发 focusin 事件时才起作用。
我在我的代码中使用了这些事件。不确定是否需要全部。
inputElement.dispatchEvent(new Event('focus'));
inputElement.dispatchEvent(new Event('focusin'));
inputElement.dispatchEvent(new Event('input'));
inputElement.dispatchEvent(new Event('keydown'));
您需要将此添加到您的 sendInput 函数中...
@Adam's comment to previous answer led me to the mat-autocomplete component's own test, specially here。您可以在哪里看到 focusin
是打开 "options".
但它们实际上在您的组件外部的覆盖层中打开,所以在我的测试中 fixture.nativeElement.querySelectorAll('mat-option').length
是 0
但是如果我查询元素 document.querySelectorAll('mat-option')
我得到了预期的选项数量.
总结一下:
fixture.detectChanges();
const inputElement = fixture.debugElement.query(By.css('input')); // Returns DebugElement
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
inputElement.nativeElement.value = text;
inputElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(3,
'Expect to have less options after input text and filter');
额外的球:如果你想点击一个选项(我点击了)你可以继续这样:
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
fixture.detectChanges();
虽然我没有成功点击并将值输入到输入中。嗯,我不是专家测试人员,但可能这种行为应该包含在自己的 mat-autocomplete
测试中(实际上是)并依赖它?
我在这里进一步建立@David 的回答。
提供正在测试的组件有@Output() selectedTimezone = new EventEmitter<string>();
,
并在组件模板中
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectTimezone($event.option.value)">
,
捕获具有正确值的适当类型事件的单元测试如下
it('should emit selectedTimezone event on optionSelected', async() => {
// Note: 'selectedTimezone' is @Output event type as specified in component's signature
spyOn(component.selectedTimezone, 'emit');
const inputElement = fixture.debugElement.query(By.css('input'));
inputElement.nativeElement.dispatchEvent(new Event('focusin'));
/**
* Note, mat-options in this case set up to have array of ['Africa/Accra (UTC
* +01:00)', 'Africa/Addis_Ababa (UTC +01:00)', 'Africa/Algiers (UTC +01:00)',
* 'Africa/Asmara (UTC +01:00)']. I am setting it up in 'beforeEach'
*/
inputElement.nativeElement.value = 'Africa';
inputElement.nativeElement.dispatchEvent(new Event('input'));
await fixture.whenStable();
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBe(4);
const optionToClick = matOptions[0] as HTMLElement;
optionToClick.click();
// With this expect statement we verify both, proper type of event and value in it being emitted
expect(component.selectedTimezone.emit).toHaveBeenCalledWith('Africa/Accra');
});
感谢@David 的回答,我在选择选项部分之前一切正常。
为了使选项选择起作用,我必须这样做;
...
matOptions[0].dispatchEvent(new Event('click'));
...
而且您不必将 matOptions[0]
的类型转换为 HTMLElement
注意**我使用的是 Angular 8.0.1,新版本中的解决方案可能有所不同