测试角度指令

时间:2019-08-19 13:38:57

标签: angular unit-testing karma-jasmine angular-directive

我有一个角度指令,该指令附加到元素上的事件:

@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作为参数发送。

  

我遵循了以下示例:https://codecraft.tv/courses/angular/unit-testing/directives/,但不幸的是它没有使用事件参数。

修改

我还遵循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触发事件,它将起作用。因此,以下代码确实将事件发送至指令,只是不太确定其 right 方式是否正确:

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;
}

3 个答案:

答案 0 :(得分:0)

您在那里得到的是空值,因为您将

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

我认为是错字,应该是

debugElement.triggerEventHandler('click', null);

如果您在此处传递对象,则会看到该对象已记录在指令中

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

我个人认为我会通过“执行”实际DOM对象的单击来进行此测试,因此您无需自己指定/添加事件,这似乎使测试更加值得信赖。

所以您将代替triggerEventHandler

debugElement.nativeElement.click()

答案 1 :(得分:0)

  

Edit 2 中,引发自定义事件可以解决问题,并通过指令的附加元素进行发送

根据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;
}

答案 2 :(得分:0)

您不直接测试指令。您可以通过测试组件测试它们。在这里,我正在测试此处描述的 *ngVar 指令 - How to declare a variable in a template in Angular

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

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