在QUnit测试中,只有在测试运行之前获得参考的情况下,click事件才会触发

时间:2019-03-10 19:43:46

标签: javascript event-handling qunit

我有以下要用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。我尚未达到可以或愿意更改测试代码的阶段。

1 个答案:

答案 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元素。该文档指出,它应该在下一次测试之前重新克隆原始文件,但是在完成测试后它不会还原原始文件。

除了btnBeforeQUnitbtn之外,btnAfterQUnitbtnDelayedAfterQUnit的第二级父级也输出到控制台,以更准确地演示何时发生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;

documentation says

  

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属性用于预定义的事件监听器

对于遇到的特定问题(想要在测试之前设置事件侦听器),可以使用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>

Mat in chat提到了使用HTML属性。