如何在 angular2 单元测试中更改 select 框的值?
How to change value of a select box in angular2 unit test?
我有一个 Angular2 组件,其中包含一个 select 框,看起来像
<select [(ngModel)]="envFilter" class="form-control" name="envSelector" (ngModelChange)="onChangeFilter($event)">
<option *ngFor="let env of envs" [ngValue]="env">{{env}}</option>
</select>
我正在尝试为 ngModelChange 事件编写单元测试。这是我最近一次失败的尝试
it("should filter and show correct items", async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
el = fixture.debugElement.query(By.name("envSelector"));
fixture.detectChanges();
makeResponse([hist2, longhist]);
comp.envFilter = 'env3';
el.triggerEventHandler('change', {});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.displayedHistory).toEqual(longhist);
});
});
我遇到麻烦的部分是更改基础模型的值 comp.envFilter = 'env3';
不会触发 change 方法。我添加了 el.triggerEventHandler('change', {});
但这抛出 Failed: Uncaught (in promise): ReferenceError: By is not defined
。我在文档中找不到任何提示...有什么想法吗?
就错误而言。看来您只需要导入 By
。这不是全球性的。它应该从以下模块导入
import { By } from '@angular/platform-browser';
就测试部分而言,这是我能够弄清楚的。当您更改组件中的值时,您需要触发更改检测以更新视图。您使用 fixture.detectChanges()
执行此操作。完成此操作后,通常 应使用该值更新视图。
从测试类似于您的示例的内容来看,情况似乎并非如此。似乎在更改检测之后仍有一些异步任务正在进行。假设我们有以下
const comp = fixture.componentInstance;
const select = fixture.debugElement.query(By.css('select'));
comp.selectedValue = 'a value';
fixture.DetectChanges();
expect(select.nativeElement.value).toEqual('1: a value');
这似乎不起作用。似乎有一些异步正在进行,导致尚未设置该值。所以我们需要通过调用 fixture.whenStable
来等待异步任务
comp.selectedValue = 'a value';
fixture.DetectChanges();
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
});
以上方法可行。但现在我们需要触发更改事件,因为它不会自动发生。
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
// component expectations here
});
});
现在我们有另一个来自事件的异步任务。所以我们需要再次稳定它
下面是我测试过的完整测试。它是 source code integration tests 示例的重构。他们使用 fakeAsync
和 tick
,这类似于使用 async
和 whenStable
。但是对于 fakeAsync
,你不能使用 templateUrl
,所以我认为最好将其重构为使用 async
。
源代码测试也进行了一种双重单向测试,首先测试要查看的模型,然后要测试要查看的模型。虽然看起来您的测试正在尝试进行某种双向测试,从模型到模型。所以我对其进行了一些重构以更好地适应您的示例。
import { Component } from '@angular/core';
import { TestBed, getTestBed, async } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { dispatchEvent } from '@angular/platform-browser/testing/browser_util';
@Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity" (ngModelChange)="onSelected($event)">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
onSelected(value) {
}
}
describe('component: NgModelSelectForm', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ NgModelSelectForm ]
});
});
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
});
}));
});
看这个例子,来自 angular 来源 (template_integration_spec.ts)
@Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const nycOption = fixture.debugElement.queryAll(By.css('option'))[1];
// model -> view
expect(select.nativeElement.value).toEqual('1: Object');
expect(nycOption.nativeElement.selected).toBe(true);
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
// view -> model
expect(comp.selectedCity['name']).toEqual('Buffalo');
}));
我发现 peeskillet 的回答非常有用,但遗憾的是它有点过时了,因为发送事件的方式已经改变。我还发现对 whenStable() 进行了不必要的调用。所以这是使用 peeskillet 设置的更新测试:
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
select.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
}));
希望这会对某人有所帮助。与 OP 提出的问题相同,但代码略有不同。
在 Angular 7.
中工作
HTML:
<select id="dashboard-filter" class="form-control" name="dashboard-filter" [ngModel]="dashboardFilterValue" (ngModelChange)="onFilterChange($event)"
[disabled]="disabled">
<option *ngFor="let filter of dashboardFilters" [ngValue]="filter.value">{{ filter.name }}</option>
</select>
单元测试:
it('onFilterChange', () => {
// ensure dropdown is enabled
expect(component.disabled).toBe(false)
// spies
spyOn(component, 'onFilterChange').and.callThrough()
spyOn(component.filterChange, 'emit')
// initially the 3rd item in the dropdown is selected
const INITIAL_FILTER_INDEX = 2
// we want to select the 5th item in the dropdown
const FILTER_INDEX = 4
// the expected filter value is the value of the 5th dashboard filter (as used to populate the dropdown)
const EXPECTED_FILTER_VALUE = getDashboardFiltersData.dashboardFilters[FILTER_INDEX].value
// handle on the dropdown
const filterDropdown = fixture.debugElement.query(By.css('select')).nativeElement
// let bindings complete
fixture.whenStable().then(() => {
// ensure filterDropdown.value is stable
expect(filterDropdown.value).toContain(getDashboardFiltersData.dashboardFilters[INITIAL_FILTER_INDEX].value)
// update filterDropdown.value and dispatch change event
filterDropdown.value = filterDropdown.options[FILTER_INDEX].value
filterDropdown.dispatchEvent(new Event('change'))
// check component data
expect(component.dashboardFilterValue).toBe(EXPECTED_FILTER_VALUE)
expect(component.dashboardFilterChangeInProgress).toBe(false)
// check spies
expect(component.onFilterChange).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(setDashboardFilterSpy).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(component.filterChange.emit).toHaveBeenCalledWith(true)
})
})
我有一个 Angular2 组件,其中包含一个 select 框,看起来像
<select [(ngModel)]="envFilter" class="form-control" name="envSelector" (ngModelChange)="onChangeFilter($event)">
<option *ngFor="let env of envs" [ngValue]="env">{{env}}</option>
</select>
我正在尝试为 ngModelChange 事件编写单元测试。这是我最近一次失败的尝试
it("should filter and show correct items", async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
el = fixture.debugElement.query(By.name("envSelector"));
fixture.detectChanges();
makeResponse([hist2, longhist]);
comp.envFilter = 'env3';
el.triggerEventHandler('change', {});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.displayedHistory).toEqual(longhist);
});
});
我遇到麻烦的部分是更改基础模型的值 comp.envFilter = 'env3';
不会触发 change 方法。我添加了 el.triggerEventHandler('change', {});
但这抛出 Failed: Uncaught (in promise): ReferenceError: By is not defined
。我在文档中找不到任何提示...有什么想法吗?
就错误而言。看来您只需要导入 By
。这不是全球性的。它应该从以下模块导入
import { By } from '@angular/platform-browser';
就测试部分而言,这是我能够弄清楚的。当您更改组件中的值时,您需要触发更改检测以更新视图。您使用 fixture.detectChanges()
执行此操作。完成此操作后,通常 应使用该值更新视图。
从测试类似于您的示例的内容来看,情况似乎并非如此。似乎在更改检测之后仍有一些异步任务正在进行。假设我们有以下
const comp = fixture.componentInstance;
const select = fixture.debugElement.query(By.css('select'));
comp.selectedValue = 'a value';
fixture.DetectChanges();
expect(select.nativeElement.value).toEqual('1: a value');
这似乎不起作用。似乎有一些异步正在进行,导致尚未设置该值。所以我们需要通过调用 fixture.whenStable
comp.selectedValue = 'a value';
fixture.DetectChanges();
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
});
以上方法可行。但现在我们需要触发更改事件,因为它不会自动发生。
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
// component expectations here
});
});
现在我们有另一个来自事件的异步任务。所以我们需要再次稳定它
下面是我测试过的完整测试。它是 source code integration tests 示例的重构。他们使用 fakeAsync
和 tick
,这类似于使用 async
和 whenStable
。但是对于 fakeAsync
,你不能使用 templateUrl
,所以我认为最好将其重构为使用 async
。
源代码测试也进行了一种双重单向测试,首先测试要查看的模型,然后要测试要查看的模型。虽然看起来您的测试正在尝试进行某种双向测试,从模型到模型。所以我对其进行了一些重构以更好地适应您的示例。
import { Component } from '@angular/core';
import { TestBed, getTestBed, async } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { dispatchEvent } from '@angular/platform-browser/testing/browser_util';
@Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity" (ngModelChange)="onSelected($event)">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
onSelected(value) {
}
}
describe('component: NgModelSelectForm', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ NgModelSelectForm ]
});
});
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
});
}));
});
看这个例子,来自 angular 来源 (template_integration_spec.ts)
@Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const nycOption = fixture.debugElement.queryAll(By.css('option'))[1];
// model -> view
expect(select.nativeElement.value).toEqual('1: Object');
expect(nycOption.nativeElement.selected).toBe(true);
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
// view -> model
expect(comp.selectedCity['name']).toEqual('Buffalo');
}));
我发现 peeskillet 的回答非常有用,但遗憾的是它有点过时了,因为发送事件的方式已经改变。我还发现对 whenStable() 进行了不必要的调用。所以这是使用 peeskillet 设置的更新测试:
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
select.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
}));
希望这会对某人有所帮助。与 OP 提出的问题相同,但代码略有不同。
在 Angular 7.
中工作HTML:
<select id="dashboard-filter" class="form-control" name="dashboard-filter" [ngModel]="dashboardFilterValue" (ngModelChange)="onFilterChange($event)"
[disabled]="disabled">
<option *ngFor="let filter of dashboardFilters" [ngValue]="filter.value">{{ filter.name }}</option>
</select>
单元测试:
it('onFilterChange', () => {
// ensure dropdown is enabled
expect(component.disabled).toBe(false)
// spies
spyOn(component, 'onFilterChange').and.callThrough()
spyOn(component.filterChange, 'emit')
// initially the 3rd item in the dropdown is selected
const INITIAL_FILTER_INDEX = 2
// we want to select the 5th item in the dropdown
const FILTER_INDEX = 4
// the expected filter value is the value of the 5th dashboard filter (as used to populate the dropdown)
const EXPECTED_FILTER_VALUE = getDashboardFiltersData.dashboardFilters[FILTER_INDEX].value
// handle on the dropdown
const filterDropdown = fixture.debugElement.query(By.css('select')).nativeElement
// let bindings complete
fixture.whenStable().then(() => {
// ensure filterDropdown.value is stable
expect(filterDropdown.value).toContain(getDashboardFiltersData.dashboardFilters[INITIAL_FILTER_INDEX].value)
// update filterDropdown.value and dispatch change event
filterDropdown.value = filterDropdown.options[FILTER_INDEX].value
filterDropdown.dispatchEvent(new Event('change'))
// check component data
expect(component.dashboardFilterValue).toBe(EXPECTED_FILTER_VALUE)
expect(component.dashboardFilterChangeInProgress).toBe(false)
// check spies
expect(component.onFilterChange).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(setDashboardFilterSpy).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(component.filterChange.emit).toHaveBeenCalledWith(true)
})
})