Как изменить значение поля выбора в модульном тесте 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. Я не могу найти никаких подсказок в документации... есть идеи?


person Paul Becotte    schedule 30.09.2016    source источник


Ответы (4)


Что касается ошибки. Похоже, вам просто нужно импортировать 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');
      });
    });
  }));
});
person Paul Samsotha    schedule 30.09.2016
comment
Этот ответ устарел из-за изменений синтаксиса. Обратитесь к ответу Иэна - stackoverflow.com/a/47391665/2959821 - person Prakash M.; 07.01.2020

Я нашел ответ 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');
        });
    }));
person Iain    schedule 20.11.2017
comment
Спасибо, Иэн: следующее зажгло мой модульный тест, установив select.nativeElement.selectedIndex: select.nativeElement.dispatchEvent(new Event('change')); :-) - person Kieran Ryan; 03.09.2020

Посмотрите этот пример из источника 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');
     }));
person Clayton K. N. Passos    schedule 01.11.2016
comment
Пожалуйста, объясните, почему этот пример отвечает на вопрос. - person Arashsoft; 01.11.2016
comment
Этот ответ помог, особенно часть '2: Object', но я не знаю причин его использования. - person camden_kid; 15.03.2018
comment
Ссылка на источник github.com/ угловой/угловой/клякса/ - person altso; 29.04.2019
comment
Этот ответ устарел из-за изменений синтаксиса. Обратитесь к ответу Иэна - stackoverflow.com/a/47391665/2959821 - person Prakash M.; 07.01.2020

Надеюсь, это поможет кому-то. Та же проблема, что и у OP, но немного другой код.

Работает в Ангуляр 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)
  })
})
person danday74    schedule 03.07.2019
comment
Необходимость делать .dispatchEvent(new Event('change')) вручную действительно ужасна. Однако никакой критики в отношении этого ответа. Счастлив, что нашел его. - person Mike de Klerk; 07.05.2020