Please note, this is a STATIC archive of website www.htmlgoodies.com from 16 Nov 2022, cach3.com does not collect or store any user information, there is no "phishing" involved.
Wednesday, November 16, 2022

Testing DOM Events Using jQuery and Jasmine 2.0

A lot of processes within a Web application are triggered by Document Object Model (DOM) events. These include button clicks, setting the focus on an element, document loading, unloading, and any other event resulting from a user action or the browser. That’s why any good Unit Testing Framework will provide methods to deal with DOM events. Jasmine is no exception. It makes it possible to test that a given event occurred and what the results of the event were. In today’s article, we’re going to create some tests for the button click event using Jasmine 2.0, jQuery, and an additional third-party library.

What You’ll Need

The add-on library that we’ll be using is built on Jasmine 2.0 and jQuery and is called jasmine-jquery. Here are the details on it and the other two libraries:

  • Jasmine 2.0: In this new major release, the makers of Jasmine have dropped the runs() and waitsFor() methods in favor of the Mocha done() callback, which has been added to beforeEach(), it() and afterEach(). If you plan on performing any tests on asynchronous processes, then this version is a must.
  • Jasmine-jQuery: There are a lot of third party libraries that enhance Jasmine’s basic functionality. The one we’ll be using here, jasmine-jquery, adds a set of custom matchers for jQuery as well as an API for creating and managing HTML, CSS, and JSON fixtures in your specs. All of the functionality is contained in the jasmine-jquery.js file so all you need to do is include it with your other JS files and reference it from your SpecRunner.html file using a <SCRIPT> tag with the SRC attribute.
  • jQuery v1.7+: Version 1.7 is where jQuery added the on() event handler. Without it, the jasmine-jquery script will raise errors. It should work with newer versions as well. Just don’t go back to any versions previous to 1.7 and you should not encounter any issues.

Setting Up Your Tests

The tests go in an HTML file called the Spec Runner. In it, you reference the required libraries and place a local script in the document that will run your tests. In the following sample file the global setFixtures() method creates the DOM from an HTML source string. Typically, your target code will be contained in existing files. These can be loaded using the loadFixtures() method. Unlike setFixtures() (seen below), it takes a URL as an argument, rather than a string:

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Jasmine v2.0 DOM Tests Spec Runner</title>

  <link rel="shortcut icon" type="image/png" href="images/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-core/jasmine.css">

  <script type="text/javascript" src="lib/jasmine-core/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jasmine-html.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/boot.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jquery-1.7.2.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jasmine-jquery.js"></script>
  
  <script type="text/javascript">
   var MSG = "Hello World!";
     
   function hideMessage() {
     $( "#pMsg" ).html("");
   }
	
   function showMessage() {
     $( "#pMsg" ).html(MSG); 
   }
	 
   function setUpHTMLFixture() {
     setFixtures('<form id="testForm" action="">'
                +'  <h1>Test Form</h1>'
                +'  <input type="text" id="txtMessage">'
                +'  <br>'
                +'  <button id="btnHideMessage" type="button" onclick="hideMessage()">Hide Message</button>'
                +'  <button id="btnShowMessage" type="button" onclick="showMessage()">Show Message</button>'
                +'  <br>'
                +'  <p id="pMsg"></p>'
                +'</form>');
      
   }
  </script>
</head>
<body>
</body>
</html>

The HTML fixture above is comprised of a form with a text field, a button that displays the contents of the text field in a paragraph and another button that clears the message.

Testing That A DOM Event Was Fired

Jasmine utilizes spies much like JUnit does Mocks, except that the former does not pretend to be a different object type. Rather, Jasmine spies simply listen for a given event and report whether or not it was triggered and, if so, by what element.

A spy is instantiated by the spyOnEvent() method. It accepts a selector string and the name of the event, sans the “on”. We can then verify that the given event was called by invoking the toHaveBeenTriggered() method on expects(spy). It is also possible to check that the event was triggered on the correct element by applying toHaveBeenTriggeredOn() to expect(), passing in the element selector:

describe("Button Click Event Tests", function() {
  var spyEvent;
  
  beforeEach(function() {
    setUpHTMLFixture();
  });
     
  it ("should invoke the btnShowMessage click event.", function() {
    spyEvent = spyOnEvent('#btnShowMessage', 'click');
    $('#btnShowMessage').trigger( "click" );
      
    expect('click').toHaveBeenTriggeredOn('#btnShowMessage');
    expect(spyEvent).toHaveBeenTriggered();
  });
     
  it ("should invoke the btnHideMessage click event.", function() {
    spyEvent = spyOnEvent('#btnHideMessage', 'click');
    $('#btnHideMessage').trigger( "click" );
      
    expect('click').toHaveBeenTriggeredOn('#btnHideMessage');
    expect(spyEvent).toHaveBeenTriggered();
  });
});

Verifying the Results of an Action

Knowing that an event was triggered by the correct element a fine start, but ultimately, we’re interested in the result of the action. Did it perform as expected or blow up? That’s where jasmine-jquery’s numerous jQuery matchers come into play. In our case, we want to know if the pMsg paragraph contains our message when the Show Message button is clicked and if it’s cleared when the Hide Message button is clicked. Like the spy checkers above, the jQuery matchers are also members of the expect() return object. One that we can use is toHaveText(). It compares the element’s text to the value passed in to the function. In the example below, the Show Message and Hide Message events are separated into two describe blocks:

var MSG = "Hello World!";
describe("Show message tests", function() {
  beforeEach(function() {
    setUpHTMLFixture();
    $('#txtMessage').val(MSG);
    $('#btnShowMessage').trigger( "click" );
  });
  
  it ("should display the message when button is clicked.", function() {
    expect($('#pMsg')).toHaveText($('#txtMessage').val());
  });
});
 
describe("Hide message tests", function() {
  beforeEach(function() {
    setUpHTMLFixture();
    $('#pMsg').text(MSG);
    $('#btnHideMessage').trigger( "click" );
  });
  
  it ("should remove the message when button is clicked.", function() {
    expect($('#pMsg')).toHaveText("");
  });
});

Here is the complete source of the SpecRunner.html file that we created today:

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Jasmine v2.0 DOM Tests Spec Runner</title>

  <link rel="shortcut icon" type="image/png" href="images/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-core/jasmine.css">

  <script type="text/javascript" src="lib/jasmine-core/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jasmine-html.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/boot.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jquery-1.7.2.js"></script>
  <script type="text/javascript" src="lib/jasmine-core/jasmine-jquery.js"></script>
  
  <script type="text/javascript">
     var MSG = "Hello World!";
     
     function hideMessage() {
       $( "#pMsg" ).html("");
     }
	
     function showMessage() {
       $( "#pMsg" ).html(MSG); 
     }
	 
     function setUpHTMLFixture() {
       setFixtures('<form id="testForm" action="">'
                  +'  <h1>Test Form</h1>'
                  +'  <input type="text" id="txtMessage">'
                  +'  <br>'
                  +'  <button id="btnHideMessage" type="button" onclick="hideMessage()">Hide Message</button>'
                  +'  <button id="btnShowMessage" type="button" onclick="showMessage()">Show Message</button>'
                  +'  <br>'
                  +'  <p id="pMsg"></p>'
                  +'</form>');
      
    }

describe("DOM TESTS:***************", function() { 
  describe("Button Click Event Tests", function() {
    var spyEvent;
	  
    beforeEach(function() {
      setUpHTMLFixture();
    });
      
    it ("should invoke the btnShowMessage click event.", function() {
      spyEvent = spyOnEvent('#btnShowMessage', 'click');
      $('#btnShowMessage').trigger( "click" );
	      
      expect('click').toHaveBeenTriggeredOn('#btnShowMessage');
      expect(spyEvent).toHaveBeenTriggered();
    });
      
    it ("should invoke the btnHideMessage click event.", function() {
      spyEvent = spyOnEvent('#btnHideMessage', 'click');
      $('#btnHideMessage').trigger( "click" );
	      
      expect('click').toHaveBeenTriggeredOn('#btnHideMessage');
      expect(spyEvent).toHaveBeenTriggered();
    });
  });
  	  
  describe("Show message tests", function() {
    beforeEach(function() {
      setUpHTMLFixture();
      $('#txtMessage').val(MSG);
      $('#btnShowMessage').trigger( "click" );
    });
    
    it ("should display the message when button is clicked.", function() {
      expect($('#pMsg')).toHaveText($('#txtMessage').val());
    });
  });
   
  describe("Hide message tests", function() {
    beforeEach(function() {
      setUpHTMLFixture();
      $('#pMsg').text(MSG);
      $('#btnHideMessage').trigger( "click" );
    });
    
    it ("should remove the message when button is clicked.", function() {
      expect($('#pMsg')).toHaveText("");
    });
  });
});
</script>
</head>
<body>
</body>
</html>

Conclusion

Testing more complex events often introduces an asynchronous element, requiring the use of Jasmine 2.0’s new done() function. We’ll cover how to use it next time.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured