Единичное тестирование события 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 минуты:)