Как изменить значение поля выбора в модульном тесте 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 14

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');
        });
    }));