我有以下要用Qunit测试的代码。
// my code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
我的QUnit测试获取对该按钮的引用并调用click函数:
(function () {
"use strict";
// HACK: with this line here click works
//var btn = document.getElementById('saveButton');
// the test
QUnit.test("opslaan", function (assert) {
// with this line no click
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'buttin found');
});
}());
奇怪的是,如果我在Qunit的测试方法中调用getElementById
,则不会调用附加到按钮的事件处理程序。但是,如果我将调用移到测试功能之外的getElementById
,则该事件会触发click事件处理程序。
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 , 'buttin 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。我尚未达到可以或愿意更改测试代码的阶段。
答案 0 :(得分:4)
执行QUnit.test()
时,似乎<div id="qunit-fixture">
下的DOM中的所有内容均已克隆并替换。这意味着您在调用之前添加的事件侦听器与DOM中存在的<button>
位于不同的位置。 btn.click();
之所以有效,是因为它在原始<button>
上触发了事件,您已经在其中保存了原始var btn =
的引用。
如果在btn
中定义了QUnit.test()
,则它引用了新的<button>
,后者没有为其分配事件监听器。 QUnit预计将主要与jQuery一起使用,因此它可能会使用jQuery .clone()
,可以将其设置为复制基于jQuery的事件侦听器,但不能复制原始的JavaScript侦听器,因为底层的Node.cloneNode()
不会没有那种能力。
您可以确认btn
中的console.log(btn.parentNode.parentNode);
已将原始QUnit.test()
与DOM断开连接。原始null
的输出为btn
,而<body>
中存在的那个为QUnit.test()
。为了证明这一点,在下面的代码中将在运行测试之前确定的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(字符串)|默认值:未定义
定义在夹具容器中使用的HTML内容,该容器在每次测试开始时都会重置。
默认情况下,QUnit将使用#qunit-fixture的任何起始内容作为灯具重置。如果您不想在两次测试之间重置灯具,请将其设置为null。
但是,将此选项设置为null
时应格外小心。这将意味着DOM在设置为null
的情况下并非对所有运行的测试都是独立的。换句话说,您在一个测试中对DOM所做的更改将影响其他测试中的更改。
IMO,QUnit.test()
在克隆DOM中的总体行为没有明确记录。绝对应该在主文档中提及该行为。特别是副作用。应该明确提及现有的事件侦听器,但在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属性定义侦听器(例如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>