Единичное тестирование события click в Angular

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

Ответы

Ответ 1

  Моя цель - проверить, вызывается ли 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()

Смотрите также:

Ответ 2

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

Ниже приведен очень простой пример тестирования события click с использованием 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 Docs:

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

Есть ограничения. Например, вы не можете сделать вызов fakeAsync из fakeAsync

Ответ 3

Я использую 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();
});

И это работало просто отлично.

Ответ 4

У меня была аналогичная проблема (подробное объяснение ниже), и я решил ее (в 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..
  }));

P.S. Великое объяснение fakeAsync и общих асинхронных тестов можно найти здесь: видео по стратегиям тестирования с помощью Angular 2 - Julie Ralph, начиная от 8:10, продолжительностью 4 минуты:)