测试 Angular 个指令

Testing Angular Directives

我有一个附加到元素事件的 angular 指令:

@Directive({
    selector: '[myDirective]'
})
export class MyDirective {
    @HostListener('click', ['$event']) click(event: Event): void {
        debugger;
        console.log(event); // <-- this is null in unit tests, MouseEvent when running the app normally
    }
}

这工作正常,但由于某些原因,在对指令进行单元测试时事件参数是 null

我的 Karma Jasmine 单元测试设置:

import { CommonModule } from '@angular/common';
import { Component, DebugElement, ElementRef, Injector } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@Component({
    selector: 'host-component',
    template: `
        <input type="text" myDirective id="findMe"/>
    `
})
class HostComponent {
}

describe(`MyDirective`, () => {
    let host: HostComponent;
    let fixture: ComponentFixture<HostComponent>;

    let debugElement: DebugElement;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [
                CommonModule
            ],
            declarations: [
                HostComponent, MyDirective
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(HostComponent);
        host = fixture.componentInstance;

        debugElement = fixture.debugElement.query(By.css('#findMe'))

        fixture.autoDetectChanges();
    });

    describe(`should listen to the events`, () => {
        it(`should listen to the click event`, () => {
            fixture..triggerEventHandler('click', null);

            fixture.detectChanges();
            // expect...
        });
    });
});

现在,问题是:指令在单元测试中被命中,但没有 event 作为参数发送。

I've followed this example: https://codecraft.tv/courses/angular/unit-testing/directives/ but unfortunately it doesnt use the event parameter.

编辑

我还按照 this example 将参数传递给 @HostListener() 装饰器:

@HostListener('mouseenter', ['$event.target.id']) 
    onMouseEnter(id: string) {
        // Logs the id of the element 
        // where the event is originally invoked.  
    console.log(id);
}

编辑 2

似乎从 DebugElement 引发的事件并不真正代表来自 DOM 元素的实际事件侦听器?

根据 Hojou 所说 on this angular github issue,如果您从 nativeElement 触发事件,它就会起作用。所以下面的代码确实通过事件发送到指令,只是不太确定它是否是正确的方式:

describe(`should listen to the events`, () => {
    it(`should listen to the click event`, () => {
        // fixture.triggerEventHandler('click', null);
        debugElement.nativeElement.dispatchEvent(newEvent('click'));

        fixture.detectChanges();
        // expect...
    });
});

function newEvent(eventName: string) {
    const customEvent: CustomEvent<any> = document.createEvent('CustomEvent');  // MUST be 'CustomEvent'
    customEvent.initCustomEvent(eventName, false, false, null);
    return customEvent;
}

你在那里得到 null 因为你在

中将 null 作为参数传递
fixture..triggerEventHandler('click', null);

我认为是错字,应该是

debugElement.triggerEventHandler('click', null);

如果你在那里传递一个对象,你会看到它被记录在指令中

debugElement.triggerEventHandler('click', {test: 'test'});

我想补充一点,我个人会通过 'executing' 单击实际的 DOM 对象来进行此测试,因此您不需要自己指定/存根事件,这似乎做一个更值得信赖的测试。

所以你可以代替 triggerEventHandler 行来做类似

的事情
debugElement.nativeElement.click()

From the Edit 2, raising a custom event does the trick and sends through the attached element of the directive

根据 Hojou 在 this angular github issue (#22148, currently 'Open') 上所说的,如果您从 nativeElement 触发事件,它就会起作用。所以下面的代码确实通过事件发送到指令,只是不太确定它是否正确:

describe(`should listen to the events`, () => {
    it(`should listen to the click event`, () => {
        // fixture.triggerEventHandler('click', null);
        debugElement.nativeElement.dispatchEvent(newEvent('click'));

        fixture.detectChanges();
        // expect...
    });
});

function newEvent(eventName: string) {
    const customEvent: CustomEvent<any> = document.createEvent('CustomEvent');  // MUST be 'CustomEvent'
    customEvent.initCustomEvent(eventName, false, false, null);
    return customEvent;
}

您不直接测试指令。您通过测试组件测试它们。我在这里测试这里描述的 *ngVar 指令 -

这里的主要内容是测试指令时使用与测试组件时完全相同的方法。并根据测试组件执行它应该执行的操作来测试指令的行为!

import { NgVarDirective } from './ng-var.directive';
import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DirectivesModule } from '../directives.module';
import { By } from '@angular/platform-browser';

@Component({
    template: '<ng-container *appNgVar="42 as myVar"><div>{{ myVar }}</div></ng-container>'
})
class TestComponent { }

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

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent],
            imports: [DirectivesModule]
        });

        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });

    it('should create an instance (HTMLElement)', () => {
        const el: HTMLElement = fixture.debugElement.nativeElement;
        const div: HTMLDivElement = el.querySelector('div');
        expect(div.textContent).toBe('42');
    });

    it('should create an instance (DebugElement)', () => {
        const el: DebugElement = fixture.debugElement;
        const de: DebugElement = el.query(By.css('div'));
        expect(de.nativeElement.textContent).toBe('42');
    });
});

这里是另一个例子(它没有测试结构指令,不像我上面的例子)https://codecraft.tv/courses/angular/unit-testing/directives