我正在使用open-wc对用lit-element编写的自定义元素进行单元测试。我们正在使用Karma,Mocha,Sinn和Chai。我正在尝试测试元素的构造函数:
constructor() {
super();
window.addEventListener(
'click',
this._handleClickOutside
);
}
供参考,测试中使用的功能是:
_handleClickOutside = () => {
console.log('calling real')
this.active = false;
};
disconnectedCallback() {
window.removeEventListener('click', this._handleClickOutside);
}
为了测试这一点,我需要对this._handleClickOutside进行存根,并检查在调度click事件时是否调用了this._handleClickOutside。这是我尝试进行的测试,其中el是已用open-wc初始化的自定义元素:
it('should add event listener to window for click', () => {
const clickEvent = new Event('click');
el.disconnectedCallback();
const spy = sinon.stub(el, '_handleClickOutside').callsFake( () => {console.log('calling fake')});
el._handleClickOutside = spy;
el.constructor();
window.dispatchEvent(clickEvent);
expect(spy.callCount).to.equal(1);
});
我知道,当我使用open-wc创建元素时,构造函数将在存根到位之前被调用,因此我尝试使用el.disconnectedCallback()删除事件监听器,对函数进行存根,然后调用构造函数再次使用存根添加事件侦听器。但是,这仍在调用实函数。
我已经能够通过在调用this._handleClickOutside的构造函数中使用匿名函数来使测试工作,但是如果我这样做,我看不到在断开的回调中删除事件监听器的方法。想知道为什么在存根就位时再次调用构造函数无法对函数进行存根。
您需要进行一些重构。您需要将_handleClickOutside
类属性方法更改为类原型方法。这样您就可以在元素实例化之前将其存根。
例如
element.ts
:
class MyElement {
active = true;
constructor() {
this._handleClickOutside = this._handleClickOutside.bind(this);
window.addEventListener("click", this._handleClickOutside);
}
_handleClickOutside() {
console.log("calling real");
this.active = false;
}
}
export default MyElement;
element.test.ts
:
import MyElement from "./element";
import sinon from "sinon";
import { expect } from "chai";
// You don't need to setup jsdom in browser test environment
// jsdom start
import jsdom from "jsdom";
const html = '<!doctype html><html><head><meta charset="utf-8">' + "</head><body></body></html>";
const url = "http://localhost";
const document = new jsdom.JSDOM(html, { url });
const window = document.window;
(global as any).document = window.document;
(global as any).window = window;
// jsdom end
describe("MyElement", () => {
afterEach(() => {
sinon.restore();
});
describe("#construtor", () => {
it("should pass", () => {
const addEventListenerStub = sinon.stub(window, "addEventListener");
const handleClickOutsideStub = sinon.stub(MyElement.prototype, "_handleClickOutside").callsFake(() => {
console.log("calling fake");
});
new MyElement();
addEventListenerStub.yield();
sinon.assert.calledWithExactly(addEventListenerStub, "click", sinon.match.func);
sinon.assert.calledOnce(handleClickOutsideStub);
});
});
describe("#_handleClickOutside", () => {
it("should pass", () => {
const el = new MyElement();
el._handleClickOutside();
expect(el.active).to.be.false;
});
});
});
单元测试结果覆盖率100%:
MyElement
#construtor
calling fake
✓ should pass
#_handleClickOutside
calling real
✓ should pass
2 passing (11ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
element.test.ts | 100 | 100 | 100 | 100 | |
element.ts | 100 | 100 | 100 | 100 | |
-----------------|----------|----------|----------|----------|-------------------|
源代码:https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59567755