Подія модульного тестування клацання в Angular


81

Я намагаюся додати модульні тести до мого додатка Angular 2. В одному з моїх компонентів є кнопка з (click)обробником. Коли користувач натискає кнопку, викликається функція, яка визначена у .tsфайлі класу. Ця функція друкує повідомлення у вікні console.log про те, що кнопку натиснуто. Мої поточні тестові тести коду для друку console.logповідомлення:

describe('Component: ComponentToBeTested', () => {
    var component: ComponentToBeTested;

    beforeEach(() => {
        component = new ComponentToBeTested();
        spyOn(console, 'log');
    });

    it('should call onEditButtonClick() and print console.log', () => {
        component.onEditButtonClick();
        expect(console.log).toHaveBeenCalledWith('Edit button has been clicked!);
    });
});

Однак це тестує лише клас контролера, а не HTML. Я не просто хочу перевірити, що реєстрація відбувається, коли onEditButtonClickвикликається; Я також хочу перевірити, що onEditButtonClickвикликається, коли користувач натискає кнопку редагування, визначену в HTML-файлі компонента. Як я можу це зробити?

Відповіді:


117

Моя мета - перевірити, чи викликається 'onEditButtonClick', коли користувач натискає кнопку редагування, а не перевіряє лише друкований console.log.

Спочатку потрібно буде налаштувати тест за допомогою Angular TestBed. Таким чином ви дійсно можете взяти кнопку та натиснути її. Що ви будете робити, це налаштувати модуль, як і ви @NgModule, просто для середовища тестування

import { TestBed, async, ComponentFixture } from '@angular/core/testing';

describe('', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ ],
      declarations: [ TestComponent ],
      providers: [  ]
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
    });
  }));
});

Потім вам потрібно підглянути onEditButtonClickметод, натиснути кнопку і перевірити, чи був викликаний метод

it('should', async(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();

  fixture.whenStable().then(() => {
    expect(component.onEditButtonClick).toHaveBeenCalled();
  });
}));

Тут нам потрібно запустити asyncтест, оскільки натискання кнопки містить асинхронну обробку подій, і потрібно зачекати, поки подія обробиться, зателефонувавшиfixture.whenStable()

Оновлення

Зараз переважно використовувати fakeAsync/tickкомбінований на відміну від async/whenStableкомбінованого. Останній слід використовувати, якщо здійснено виклик XHR, оскільки fakeAsyncвін не підтримує його. Отже, замість наведеного вище коду, реконструйованого, це могло б виглядати

it('should', fakeAsync(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();
  tick();
  expect(component.onEditButtonClick).toHaveBeenCalled();

}));

Не забудьте імпортувати fakeAsyncі tick.

Дивитися також:


2
що, якщо у нас є більше однієї кнопки у файлі html? Виконуючи щось на зразок: fixture.debugElement.nativeElement.querySelector ('кнопка');
Айгуо

2
шукає "кнопка" у файлі html, але що у нас більше, ніж одна кнопка? Як я повинен посилатися на появу другої кнопки для тестування?
Айгуо

3
Я отримав рішення! button = fixture.debugElement.queryAll (By.css ('button'); button1 = button [0];
Aiguo,

3
Я просто витратив день, намагаючись зрозуміти, чому це не працює для мене, лише щоб усвідомити, що я викликаю клік на елементі мого компонента, а не div всередині нього, який я насправді слухаю для клацань.
майнінайбу

1
у чому різниця між button.triggerEventHandler ('click', null) і button.click90?
jitenagarwal19,

48

Події можна перевірити за допомогою async/ fakeAsyncфункцій, які надаються '@angular/core/testing', оскільки будь-яка подія у браузері є асинхронною та надсилається до циклу / черги подій.

Нижче наведено дуже базовий приклад тестування події натискання за допомогою fakeAsync.

fakeAsyncФункція дозволяє лінійний стиль кодування, виконавши пробне тіло в спеціальній fakeAsyncвипробувальній зоні.

Тут я тестую метод, який викликається подією click.

it('should', fakeAsync( () => {
    fixture.detectChanges();
    spyOn(componentInstance, 'method name'); //method attached to the click.
    let btn = fixture.debugElement.query(By.css('button'));
    btn.triggerEventHandler('click', null);
    tick(); // simulates the passage of time until all pending asynchronous activities finish
    fixture.detectChanges();
    expect(componentInstance.methodName).toHaveBeenCalled();
}));

Нижче - що мають сказати документи Angular :

Принципова перевага fakeAsync над асинхронною полягає в тому, що тест здається синхронним. Немаєthen(...) порушувати видимий потік контролю. Повернення обіцянки fixture.whenStableвідсутнє, замінене наtick()

Там є обмеження. Наприклад, ви не можете здійснити дзвінок XHR зсерединиfakeAsync


Чи є підстави віддавати перевагу цьому чи прийнятому рішенню? Як кутовий нуб, я не уявляю, що "краще"
Адам Хьюз,

2
@AdamHughes fakeAsync легше читати та розуміти відповідно до кутової документації. Але ви можете вибрати все, що вам найбільше підходить. Немає нічого подібного до прийнятого рішення при використанні aync або fakeAsync.
Mav55

1
Я написав відповідь тут про порівняння між використанням asyncVS. fakeAsync. Незважаючи на те, що у своїй відповіді я використовував async, загалом, моєю перевагою було б використовувати fakeAsync.
Пол Самсота,

Ви повинні мати tick()s, лише якщо ваш код телефонує setTimeout, я не думаю, що галочка потрібна? Дивіться stackoverflow.com/a/50574080/227299
Хуан Мендес

Я віддаю перевагу цьому рішенню, оскільки воно використовує debugElement (що є селективом агностики середовища).
JoshuaTree

10

Я використовую Angular 6 . Я слідував відповіді Mav55, і це спрацювало. Однак я хотів переконатися, чи fixture.detectChanges();це дійсно потрібно, тому я видалив його, і він все ще працював. Потім я видалив, tick();щоб перевірити, чи працює це, і це так. Нарешті я витягнув тест з fakeAsync()обгортки, і здивування, це спрацювало.

Тож я закінчив із цим:

it('should call onClick method', () => {
  const onClickMock = spyOn(component, 'onClick');
  fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
  expect(onClickMock).toHaveBeenCalled();
});

І це спрацювало просто чудово.


моя загальна стратегія полягала в тому, щоб завжди викликати fixture.detectChanges(), щоб викликати події життєвого циклу angular. могло бути так, що ваш компонент не має ngOnInitабо що для проходження тесту не було необхідності.
SnailCoil

@SnailCoil або він міг використати автоматичне виявлення змін ( angular.io/guide/testing#automatic-change-detection )
Максиміліан Маєр

1
Деякі події dom є синхронними. Наприклад, вхідна подія задокументована як синхронна. Якщо ви відчуваєте подію натискання кнопки істинно, то це означає, що подія натискання кнопки також є синхронною. Але є багато подій Dom, які є асинхронними , в цьому випадку вам буде необхідно використовувати asyncабо fakeAsync. Щоб перестрахуватися, я не бачу нічого поганого в припущенні, що всі події є асинхронними і просто використовують asyncабо fakeAsync(це ваша перевага).
Пол Самсота,

Якщо ви працюєте не в повному браузері, краще використовувати By.css замість querySelector. (src stackoverflow.com/questions/44400566/… ).
Ambroise

@PaulSamsotha Здається, погана ідея смітити свій код tick()s, оскільки щось може бути асинхронним? Ви повинні знати, що є, а що не асинхронним. Запуск подій миші / клавіатури / введення завжди синхронний.
Хуан Мендес,

1

У мене була подібна проблема (детальне пояснення нижче), і я вирішив її (in jasmine-core: 2.52), використовуючи tickфункцію з такою ж (або більшою) кількістю мілісекунд, як у вихідному setTimeoutвиклику.

Наприклад, якби я мав setTimeout(() => {...}, 2500);(так він спрацює через 2500 мс), я би телефонував tick(2500), і це вирішило б проблему.

Те, що я мав у своєму компоненті, як реакція на натискання кнопки Видалити :

delete() {
    this.myService.delete(this.id)
      .subscribe(
        response => {
          this.message = 'Successfully deleted! Redirecting...';
          setTimeout(() => {
            this.router.navigate(['/home']);
          }, 2500); // I wait for 2.5 seconds before redirect
        });
  }

Це мій робочий тест:

it('should delete the entity', fakeAsync(() => {
    component.id = 1; // preparations..
    component.getEntity(); // this one loads up the entity to my component
    tick(); // make sure that everything that is async is resolved/completed
    expect(myService.getMyThing).toHaveBeenCalledWith(1);
    // more expects here..
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const deleteButton = fixture.debugElement.query(By.css('.btn-danger')).nativeElement;
    deleteButton.click(); // I've clicked the button, and now the delete function is called...

    tick(2501); // timeout for redirect is 2500 ms :)  <-- solution

    expect(myService.delete).toHaveBeenCalledWith(1);
    // more expects here..
  }));

PS Чудове пояснення fakeAsync загальних асинхронних питань у тестуванні можна знайти тут: відео про тестування стратегій з Angular 2 - Джулі Ральф, починаючи з 8:10, тривалістю 4 хвилини :)


1

щоб спочатку перевірити подію виклику кнопки, нам потрібно шпигувати за методом, який буде викликаний після натискання кнопки, тому наш перший рядок буде шпигунським Методом шпигуна візьмемо два аргументи 1) ім'я компонента 2) метод для шпигунства, тобто: 'onSubmit' запам'ятати не використовувати ' () 'потрібне лише ім'я, тоді нам потрібно зробити об'єкт кнопки, на який потрібно натиснути, тепер ми повинні запустити обробник події, до якого ми додамо подію click, тоді ми очікуємо, що наш код викличе метод submit один раз

it('should call onSubmit method',() => {
    spyOn(component, 'onSubmit');
    let submitButton: DebugElement = 
    fixture.debugElement.query(By.css('button[type=submit]'));
    fixture.detectChanges();
    submitButton.triggerEventHandler('click',null);
    fixture.detectChanges();
    expect(component.onSubmit).toHaveBeenCalledTimes(1);
});

Здається, це не працює для мене. Я використовую Angular 7. Я вважаю правильним підхід: it('should call onSubmit method',() => { let mock = spyOn(component, 'onSubmit'); let submitButton: DebugElement = fixture.debugElement.query(By.css('button[type=submit]')); fixture.detectChanges(); submitButton.triggerEventHandler('click',null); fixture.detectChanges(); expect(mock).toHaveBeenCalledTimes(1); });
Тодор Тодоров,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.