javascript - 在 QUnit 测试中,只有在测试运行之前获得引用时才会触发 click 事件

标签 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("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');
    });
}());

如果我调用 getElementById 就够奇怪了在 Qunit 的测试方法中,附加到按钮的事件处理程序不会被调用。但是,如果我将调用转移到 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 中的所有内容被克隆和替换。这意味着您在该调用之前添加的事件监听器位于不同的 <button> 上。比现在存在于 DOM 中。 btn.click();有效,因为它在原始 <button> 上触发事件您保存了对原始 var btn = 的引用.

如果 btnQUnit.test() 中定义,然后它引用新的 <button> ,它没有分配给它的事件监听器。 QUnit 预计将主要与 jQuery 一起使用,因此它可能会使用 jQuery .clone() ,可以设置为复制基于 jQuery 的事件监听器,但不能复制原生 JavaScript 监听器,因为底层的 Node.cloneNode() 没有那个能力。

可以确认原btn通过 console.log(btn.parentNode.parentNode); 与 DOM 断开连接在 QUnit.test() 内.输出 null为原btn , 但是是 <body>对于存在于 QUnit.test() 中的那个.为了证明这一点,btn在运行测试之前确定的分配给 btnBeforeQUnit在下面的代码中。

QUnit 克隆 HTML 内容是处理使测试彼此独立的好方法,但应该记录在案。通常希望单元测试是独立的。毕竟,可能存在改变 DOM 结构的测试,应该在测试之间恢复。

但是,由于某种原因,QUnit 不会在 QUnit.test() 末尾恢复原始 DOM 元素。 .文档表明它应该在下一次测试之前重新克隆原始文件,但在测试完成后它不会恢复原始文件。

除了btnBeforeQUnitbtn , btnAfterQUnit 的二级 parent 和 btnDelayedAfterQUnit也输出到控制台,以更准确地演示何时通过异步执行提供给 QUnit.test() 的回调发生 DOM 替换。 .

// 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 (string) | default: undefined

Defines the HTML content to use in the fixture container which is reset at the start of each test.

By default QUnit will use whatever the starting content of #qunit-fixture is as the fixture reset. If you do not want the fixture to be reset in between tests, set the value to null.



但是,将此选项设置为 null 时应小心谨慎。 .这意味着 DOM 对于设置为 null 时运行的所有测试都不是独立的。 .换句话说,您在一个测试中对 DOM 所做的更改将影响其他测试中的更改。

IMO,QUnit.test() 的整体行为在克隆 DOM 中没有明确记录。肯定应该在主文档中提到一些行为。特别是副作用。应该明确提及现有的事件监听器,但我在 QUIinit 文档中没有找到任何明确描述此过程及其效果的内容。

以下是相同的代码,但使用 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 提到了使用 HTML 属性。 in chat .

关于javascript - 在 QUnit 测试中,只有在测试运行之前获得引用时才会触发 click 事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55091658/

相关文章:

javascript - 如何在 javascript 中访问 JSON.parsed 对象

javascript - 有没有办法在渲染或重新渲染组件时控制日志?

Javascript 类名不出现

Javascript IE8 未在 AttachEvent 中传递 'this'

c# - 将通用事件处理程序连接到同一类型的多个控件

javascript - 如何在 qUnit 中针对 DOM 对象进行测试?

javascript - 内容隐藏在 div 之上

javascript - 日志记录对 native 事件使用react,console.log 未触发并最终使应用程序崩溃

javascript - QUnit.js 测试失败后停止

javascript - QUnit asyncTest 有什么区别?