我有这个问题的替代解决方案。如果您想知道为什么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 中,它无法访问根文档。如果那是错误的,我很想知道。
我不喜欢我的解决方案的一件事是它添加了只需要使测试成为可能的生产代码。我通常更愿意在我的测试中跳过很多圈,以避免为了测试而更改生产代码。在这种情况下,我看不到这样做的方法。