9

根据 Angular 测试文档,为了触发测试中的事件,我们使用triggerEventHandler()debug 元素上的方法。此方法采用event nameobject。现在,如果我们使用HostListener. 例如:@HostListener('mousemove', ['$event'])或添加document关卡事件,我们这样做是这样的@HostListener('document:mousemove', ['$event'])

在我当前的指令实现中,由于我无法嵌套 HostListener,我document使用document.addEventListener内部的HostListener.
代码如下:

@HostListener('mousedown', ['$event'])
callMouseDown(event){
  if (something) {
   document.addEventListener('mousemove', this.callMouseMove.bind(this));
  }
}

callMouseMove(event){
 // do some computations.
}

现在,我想触发在我的测试级别mousemove添加的事件。document的当前实现triggerEventHandler()不起作用,即在测试中未触发侦听器。

我怎样才能让它工作?任何人都可以帮助我一些指导。

编辑:添加测试:

it('test spec', inject([MyService], (service: MyService) =>{
   x = [];
  //service calls the method
  service.evtEmit.subscribe(e => {
   x.push(e);
  });
  triggerEventHandler("mousedown", {pageX: 200, pageY: 300});
  triggerEventHandler("document:mousemove", {pageX: 250, pageY: 320});
  // the above statement won't work.
  expect(arr).toEqual({x:50, y: 20});
}));
4

2 回答 2

8

似乎您描述的类似问题被记录为角度回购中的问题

您可以使用组件实例获取指令的访问权限,并可以访问方法(即,在您的情况下,它是 callMouseMove)。以下方法应该有效:

it('test spec', inject([MyService], (service: MyService) => {
   x = [];
  //service calls the method
  service.evtEmit.subscribe(e => {
   x.push(e);
  });
  triggerEventHandler("mousedown", {pageX: 200, pageY: 300});
  component.directive.callMouseMove({pageX: 250, pageY: 320});
  expect(arr).toEqual({x:50, y: 20});
}));

希望这可以帮助。

更新

我意识到如果你的指令中有私有方法,我的方法就行不通。最好创建自定义事件,或者在这种情况下以编程方式创建鼠标事件,并在文档级别或元素本身触发事件。yurzui提到了这个方法。感谢他。

以下是您的操作方法:

function programmaticTrigger(eventType: string, evtObj: any) {
   let evt = new MouseEvent(eventType, evtObj);
   document.dispatchEvent(evt);
}

您可以从规范中调用此函数:

programmaticTrigger("mousemove", {clientX: 250, clientY: 320});

请注意,在这种情况下,我没有通过pageXpageY在这里,因为它们是,readonly但它们是基于clientXandclientY值在内部计算的。

如果您想创建一个自定义事件并传入您想要的任何值,您可以执行以下操作:

function programmaticTrigger(eventType: string, evtObj: any) {
       let evt: any;
       evt = Event(eventType, evtObj);
       document.dispatchEvent(evt);
    }

调用它如下:

programmaticTrigger("mousemove", {pageX: 250, pageY: 320});

希望这可以帮助。

于 2017-08-22T21:02:46.830 回答
0

我有这个问题的替代解决方案。如果您想知道为什么ShellZero的答案对我不起作用,请参见下文。

我的解决方案是创建一个可以返回的可注入“包装器”服务document。这样,在生产中它像往常一样获取文档,但在测试中,我可以模拟包装器并提供我自己的“文档”并将事件发送给它。

文档-wrapper.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DocumentWrapperService {

  constructor() { }

  public getDocument() {
    return document;
  }
}

在我的组件中,我可以在构造函数中注入这个服务并从中获取document。就我而言,我在ngOnInit方法中使用它。

一些.component.ts

import { DocumentWrapperService } from './../../../services/document-wrapper/document-wrapper.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'some-component',
  template: '<div id="container"><!-- Your HTML here --></div>',
  styleUrls: ['./some.component.css']
})
export class SomeComponent implements OnInit {
constructor(private documentWrapper: DocumentWrapperService) { }

  ngOnInit() {
    const _document = this.documentWrapper.getDocument();

    _document.addEventListener('mouseup', ($event: MouseEvent) => {
      // do something on mouse-up
    });

}

这需要在测试中做一些额外的工作。我必须制作一个模拟包装器并注入它。默认情况下,我的模拟返回document,因此现有测试不会中断。

import { DocumentWrapperService } from './../../../services/document-wrapper/document-wrapper.service';
import * as td from 'testdouble';

describe('SomeComponent', () => {
  beforeEach(async(() => {
    // I use testdouble to make my mock, but you could roll your own or use some other library.
    mockDocumentWrapper = td.object(DocumentWrapperService.prototype);
    td.when(mockDocumentWrapper.getDocument()).thenReturn(document);

    TestBed.configureTestingModule({
      declarations: [SomeComponent],
      providers: [
        { provide: DocumentWrapperService, useValue: mockDocumentWrapper }
      ]
    })
      .compileComponents();
  }));

然后,在我测试事件处理的规范方法中,我必须设置我的模拟以返回不同的元素而不是document. 我发现的最好的事情是使用div组件本身的最外层。因为我的电话addEventListener是 in ngOnInit,所以我还得再打电话ngOnInit。完成此操作后,我可以自由地发送事件并提出我的期望。

it("should do something when the user releases the mouse button", () => {
  const rootDivElement = fixture.nativeElement.querySelector("#container");
  td.when(mockDocumentWrapper.getDocument()).thenReturn(rootDivElement);
  component.ngOnInit();

  rootDivElement.dispatchEvent(new MouseEvent('mouseup', { clientY: 100, clientX: 200 }));

  // expectations go here
});

虽然ShellZero的答案是我能找到的最好的答案,但我并不满意。在 Angular 组件上测试事件处理程序时,我认为调用组件本身的处理程序方法是不够的,因为它不能证明组件已订阅了正确的事件。我更喜欢触发事件并期望组件做出正确反应。

当我实现它时, ShellZero的答案中的“更新”部分直接不起作用。我认为这是因为 Karma 将 Angular 组件放在一个 iFrame 中,它无法访问根文档。如果那是错误的,我很想知道。

我不喜欢我的解决方案的一件事是它添加了只需要使测试成为可能的生产代码。我通常更愿意在我的测试中跳过很多圈,以避免为了测试而更改生产代码。在这种情况下,我看不到这样做的方法。

于 2020-11-14T19:39:40.197 回答