Эмуляция событий в unit-тестах Angular
Существует два способа вызова событий в юнит-тестах. Давайте рассмотрим оба из них.
1. triggerEventHandler()
Экземпляр Angular DebugElement предоставляет удобный метод для вызова событий - triggerEventHandler(). Давайте посмотрим, как мы можем его использовать.
it('should set 😜 on click', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const h1 = fixture.debugElement.query(By.css('h1'));
h1.triggerEventHandler('click', {});
fixture.detectChanges();
expect(
fixture.debugElement.query(By.css('h1')).nativeElement.innerText
).toEqual('😜');
});@Component({
template: `
<h1 (click)="onClick()">
{{ emoji }}
</h1>
`
})
export class AppComponent {
emoji: string;
onClick() {
this.emoji = '😜';
}
}
У нас есть простой тест для компонента, который, при клике, устанавливает эмодзи. Мы используем метод query() для получения ссылки на элемент и вызываем обработчик события клика с помощью метода triggerEventHandler().
Три важных факта о методе triggerEventHandler():
- Он вызовет обработчик события только в том случае, если был объявлен на элементе с помощью привязок событий Angular, декораторов @HostListener() или @Output (а также меньше используемого Renderer.listen()). Например:
@Component({
template: `
<input (keydown.enter)="onEnter($event)">
<my-component (data)="onData($event)"></my-component>
`
})
export class AppComponent {
emoji: string;
onEnter() {
this.emoji = '😜';
}
onData($event) {
this.emoji = $event.emoji;
}
}it('should set the 😜 on (data)', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const myComponent = fixture.debugElement.query(By.directive(MyComponent));
myComponent.triggerEventHandler('data', { emoji: '😜' });
fixture.detectChanges();
expect(
fixture.debugElement.query(By.css('h1')).nativeElement.innerText
).toEqual('😜');
});
it('should set the 😜 on enter', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const input = fixture.debugElement.query(By.css('input'));
input.triggerEventHandler('keydown.enter', {});
fixture.detectChanges();
expect(
fixture.debugElement.query(By.css('h1')).nativeElement.innerText
).toEqual('😜');
});Обратите внимание, что второй параметр представляет собой объект события, который будет передан обработчику.
- Он не вызовет автоматически changeDetection; мы должны вызвать его сами.
- В отличие от того, что думают или делают разработчики, нет необходимости использовать fakeAsync — Обработчик будет выполнен синхронно. Мы можем увидеть это при отладке кода:
2. Использование Javascript API
Давайте рассмотрим другие случаи, например, когда мы определяем события через API JavaScript. Например, давайте воспользуемся RxJS fromEvent:
import { untilDestroyed } from 'ngx-take-until-destroy';
@Component({
template: `
<h1 #h1>{{ emoji }}</h1>
`
})
export class AppComponent {
@ViewChild('h1') h1: ElementRef<HTMLElement>;
emoji: string;
ngAfterViewInit() {
fromEvent(this.h1.nativeElement, 'click').pipe(
untilDestroyed(this)
).subscribe(() => {
this.emoji = '😜';
});
}
}Теперь, как мы упоминали ранее, если мы используем метод triggerEventHandler()Angular, это не сработает, потому что Angular не знает об этом событии. К счастью, это событие клика, и мы можем программно вызвать клик, используя нативный метод click() элемента HTMLElement.
it('should set the 😜 on click', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const h1 = fixture.debugElement.query(By.css('h1'));
h1.nativeElement.click(); <===
fixture.detectChanges();
expect(
fixture.debugElement.query(By.css('h1')).nativeElement.innerText
).toEqual('😜');
});Конечно, но что насчет других событий, таких как mouseenter, input и т. д.? Мы можем использовать API событий в сочетании с методом dispatchEvent(), чтобы запускать события. Например:
ngAfterViewInit() {
fromEvent(this.h1.nativeElement, 'mouseenter').subscribe(() => {
this.emoji = '😜';
});
}it('should set the 😜 on mouseenter', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const h1 = fixture.debugElement.query(By.css('h1'));
const mouseenter = new MouseEvent('mouseenter');
h1.nativeElement.dispatchEvent(mouseenter);
fixture.detectChanges();
expect(
fixture.debugElement.query(By.css('h1')).nativeElement.innerText
).toEqual('😜');
});Мы снова видим, что я не использую fakeAsync, и это из-за MDN:
В отличие от нативных событий, которые генерируются DOM и вызывают обработчики событий асинхронно через цикл событий, dispatchEvent вызывает обработчики событий синхронно. Все применимые обработчики событий будут выполнены и вернутся, прежде чем код продолжит выполнение после вызова dispatchEvent.