Как изменить значение поля выбора в модульном тесте angular2?
У меня есть компонент Angular2, который содержит поле выбора, которое выглядит как
<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';
не запускает метод изменения. Я добавил el.triggerEventHandler('change', {});
, но это бросает Failed: Uncaught (in promise): ReferenceError: By is not defined
. Я не могу найти никаких намеков в документации... есть идеи?3 ответа:
Что касается ошибки. Похоже, вам просто нужно импортировать
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 }); });
Ниже приведен полный тест,который я тестировал. Это рефактор примера изтестов интеграции исходного кода . Они использовали
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'); }); }); })); });
Посмотрите этот пример, из углового источника (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'); }));
Я нашел ответ пискиллета очень полезным, но, к сожалению, он немного устарел, поскольку способ отправки события был изменен. Я также обнаружил, что был ненужный вызов 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'); }); }));