我有以下代码要用Qunit进行测试。
// my code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
我的 QUnit 测试获取对按钮的引用并调用单击函数:
(function () {
"use strict";
// HACK: with this line here click works
//var btn = document.getElementById('saveButton');
// the test
QUnit.test("click save", function (assert) {
// with this line no click
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
奇怪的是,如果我在 Qunit 的测试方法中调用getElementById
附加到按钮的事件处理程序不会被调用。但是,如果我将调用移动到测试函数之外getElementById
,该事件确实会触发单击事件处理程序。
QUnit 在那里做了什么来阻止我的测试按预期工作,以及解决我面临的问题的适当/推荐方法是什么?
这是一个MCVE(也在JSFiddle上),它演示了非工作版本。我已经评论了要更改的内容以使单击正常工作(在这里工作意味着:将文本输出到控制台)
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
//var btn = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
值得注意的是,我在这里明确不使用jQuery,因为我正在为其编写测试的代码也没有使用jQuery。我还没有达到我可以或愿意更改测试代码的阶段。
当你执行QUnit.test()
时,似乎<div id="qunit-fixture">
下面的 DOM 中的所有内容都被克隆和替换了。这意味着您在该调用之前添加的事件侦听器与 DOM 中现在存在的<button>
不同。该btn.click();
之所以有效,是因为它在原始<button>
上触发事件,您已使用原始var btn =
保存引用。
如果在QUnit.test()
中定义了btn
,则它引用新的<button>
,它没有分配事件侦听器。QUnit 预计主要与 jQuery 一起使用,因此它可能会执行 jQuery.clone()
,可以将其设置为复制基于 jQuery 的事件侦听器,但不能复制原版 JavaScript 侦听器,因为底层Node.cloneNode()
没有该功能。
您可以通过QUnit.test()
中的console.log(btn.parentNode.parentNode);
来确认原始btn
与 DOM 断开连接。这为原始btn
输出null
,但对于存在于QUnit.test()
中的<body>
。为了演示这一点,在运行测试之前确定的btn
在下面的代码中分配给btnBeforeQUnit
。
QUnit 克隆 HTML 内容是处理彼此独立的测试的好方法,但应记录在案。通常希望单元测试是独立的。毕竟,可能会有更改 DOM 结构的测试,这些测试应该在测试之间恢复。
但是,由于某种原因,QUnit 不会在QUnit.test()
结束时恢复原始的 DOM 元素。文档指示它应该在下一次测试之前重新克隆原始测试,但在测试完成后不会还原原始测试。
除了btnBeforeQUnit
和btn
之外,btnAfterQUnit
和btnDelayedAfterQUnit
的二级父级也输出到控制台,以更准确地演示DOM替换何时发生异步执行提供给QUnit.test()
的回调。
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
调整此行为
您可以通过将QUnit.config.fixture
设置为null
来获得您期望的行为:
QUnit.config.fixture = null;
文档说:
QUnit.config.fixture (string) | default: undefined
定义要在夹具容器中使用的 HTML 内容,该内容在每个测试开始时重置。
默认情况下,QUnit将使用 #qunit 夹具的起始内容作为夹具复位。如果不希望在测试之间重置夹具,请将该值设置为 null。
但是,将此选项设置为null
时应小心。这将意味着 DOM 对于设置为null
时运行的所有测试都不是独立的。换句话说,在一个测试中对 DOM 所做的更改将影响其他测试中的更改。
IMO,克隆DOM中QUnit.test()
的整体行为没有明确记录。绝对应该在主文档中提及该行为。特别是副作用。应该明确提及现有的事件侦听器,但我在 QUinit 文档中没有找到任何明确描述此过程及其效果的内容。
以下是相同的代码,但添加了QUnit.config.fixture = null;
。如您所见,"保存单击"是从测试输出到控制台的,而不是原始输出的。
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
QUnit.config.fixture = null;
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
或者,对预定义的事件侦听器使用 HTML 属性
对于您遇到的特定问题,即希望在测试之前设置事件侦听器,您可以使用 HTML 属性来定义侦听器(例如onclick="save()"
在你的情况下。
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton" onclick="save()">test</button>
</div>
使用HTML属性是Mat在聊天中提到的。