Getting Started With QUnit Testing
Written by Ian Elliot   
Thursday, 09 February 2017
Article Index
Getting Started With QUnit Testing
Testing HTML

justjquery

The Assertions

From here your task is to add assertions there are most likely to catch errors in the functions within the JavaScript file that you are testing.  As well as the ok assertion that are a number of others that you need to know about:

  • ok(bool,string) asserts that its first argument is true and displays the string.

  • equal(object1,object2,string) tests weak equality i.e. using == between two objects. 

  • strictEqual(object1,object2,string) tests strict equality i.e.using === to make sure that the objects are of the same type and value.

  • deepEqual(object1, object2,string) makes a deep recursive test for equality between two objects.

  • propEqual(object1,object2,string) compares the own properties of two objects.

There is also notOk, notEqual, notStrictEqual, notDeepEqual and notPropEqual which are sometimes easier to read than negating an assertion. 

You do have to be careful how you use these assertions otherwise you could well trigger failures that aren't justified.

If it really is ok for a function to return anything that can be treated as a value then use equal if not use strictEqual. That is

assert.equal(1,"1","A true assertion");

and

assert.strictEqual(1,"1", A false assertion);

You need to use the assertion that fits in with the way that the function you are testing is going to be used in practice.

The only assertion that might be a tiny bit complicated is  deepEqual.

This is used to test that two objects, including arrays and even strings, have the same properties with identical values. The meaning of this is clear where the values are primitive types such as numeric values but what if the value is itself an object complete with properties? In this case the deepEqual assertion is applied to the object to make sure it is deepEqual to the target value.  Notice that this is not equality of reference - i.e. the two objects can be distinct rather just the same object referenced by two variables.

For example:

var A={"A":1};
var B=A;
deepEqual(A,B,"equal reference")
deepEqual(A,{"A":1},"equal value");

both assertions pass but the first is an equality of reference, i.e. both A and B reference the same object and the second is equality of value because the object that A references is a different object to the object literal in the assertion.

The Global Namespace

There is one very important testing feature that is quick to describe, easy to use and will save you hours. If you are writing quality JavaScript code then no function should add to the global namespace unless it is doing something very strange. However all you have to do is to forget to write a var in front of the first use of a variable and the global namespace is polluted.

To detect that this has happened all you have to do is to select the Check for Globals box in the test page and the tests will be run again with an extra assertion automatically applied to every function that the global namespace should be the same before and after the function has been used.

For example if you change the max function to:

function myMax(a,b){
    c=a;
    return Math.max(a,b);
}

and run the tests again with Check for Globals on you will see the message:

5 Introduced global variable(s) c.

Testing HTML

There has been a missing set of elements in all our tests so far  - the HTML that the JavaScript often interacts with.

How can we test a function that modifies some HTML by interacting with the DOM?

The JavaScript we are testing probably works with some production HTML page and we don't want to pull this into the testing page. There is also the problem of side effects. If you run a test on a function and it modifies the DOM how might this effect other functions tested later? The issue of side effects and how to handle them is a big problem in general testing but for JavaScript it is the DOM - a global resource that is the big headache.

Fortunately QUnit gives us an easy solution. You can use a Div with the id qunit-fixture to contain any HTML you want to be available to test your functions. When you run the a test it can modify anything in the fixture div and when it has finished QUnit will restore the DOM to its state before the function ran.

That is the fixture div is your HTML playground where you can do anything you want safe in the knowledge that it will not effect anything that follows. Of course if you do want to keep side effects simply create the testing HTML in the web page and not in the fixture div.

Notice that the DOM is restored to its original state after the test is complete not after each assertion. For example, suppose we have the function:

function changeText(){
 document.getElementById("myElement").
                   innerHTML="Hello qUnit";
}

 then to test it we add to the testing page:

<div id="qunit">
</div>
<div id="qunit-fixture">
    <div id="myElement"></div>
</div>

Now we can add two tests:

QUnit.test("MyTest1", function (assert) {
 changeText();
 assert.equal(
   document.getElementById( "myElement").innerHTML,
   "Hello QUnit");
});

QUnit.test("MyTest2", function (assert) {    
  assert.equal(
   document.getElementById("myElement").innerHTML,
   "Hello QUnit");
});

 

The first test runs the function which modifies the DOM and the assertion checks that the DOM has indeed been modified. The first test then ends and the DOM is restored to its original state. When the second test starts the assertion it contains fails because the DOM element doesn't contain "Hello QUnit" because the changes have been reset.

htmltests

By grouping tests together you can construct assertions where changes propagate to later assertions and you can use separate tests to reset the DOM.

Asynchronous Tests

Sooner or later you will encounter the need to test a function that is asynchronous.

In this case you need QUnit to wait for it to complete any assertions which have become asynchronous. For example suppose myAsync accepts a callback that is run when it is complete. You might write something like:

QUnit.test("async",function(assert){
 myAsync(function(result){
  assert.equal(result,"ok");
 })
}

This is perfectly logical in that you want to test the value returned sooner or later by myAsync to the callback but as it is things will be a little strange. The reason is that QUnit will continue on its way running assertions and tests before the callback with the equal assertion is called. To keep things in the right order we can stop QUnit and restart it after the assertions have been run in the callback. 

To do this we need to use the async function which returns a done function which can be used to signal that the asynchronous call is complete.

That is:

QUnit.test("async",function(assert){ 
 var done = assert.async();
 myAsync(function(result){
  equal(result,"ok"); 
  done();
 })
}

The call to async halts the running of tests while myAsync function is working. When it finally called the callback an assertion is computed and the test engine restarted by the call to done. Notice that the call to done is within the callback function.

If the test involves more than one asynchronous call you can specify the number as a parameter in the async call. That is:

var done=assert.async(2);

specifies that two async operations will occur before the test is complete. In this case you need to call done twice to restart testing.

Where next

There are lots of other features of QUnit that make testing more powerful and better organized, but you now have the basics. You now know that you don't change the production code to test it. You simply create a test page which runs tests, each test consisting of a set of assertions. You also now know how to provide an HTML playground for the functions to exercise themselves in.

From here you need to look up the use of modules to group tests together and you need to find out how to add custom assertions, but these things can wait until you need to make use of them.

Testing and development should go hand-in-hand and now you have no excuse for not adding a testing page to your project.

Related Articles

QUnit Javascript Unit Tester Released

 

fail

 

 Available as a Book:

smallcoverjQuery

buy from Amazon

  1. Understanding jQuery
  2. Basic jQuery CSS Selectors
       Extract: The DOM
  3. More Selectors
       Extract: Basic Selectors
  4. The JQuery Object
  5. Filters 
  6. DOM Traversal Filters 
  7. Modifying DOM Objects
       Extract: Modifying The DOM 
  8. Creating Objects & Modifying The DOM Hierarchy
  9. Working With Data
       Extract: Data ***NEW!!!
  10. Forms 
  11. Function Queues
  12. Animation 
  13. jQuery UI
  14. jQuery UI Custom Control
  15. Easy Plugins 
  16. Testing With QUnit
  17. Epilog A Bonus Function

Also Available:

jquery2cover

buy from Amazon

 

Banner


JavaScript Canvas - Fetch API

Working with lower-level data is very much part of graphics. This extract from Ian Elliot's book on JavaScript Graphics looks at how to use typed arrays to access graphic data.



JavaScript Jems - The Inheritance Tax

JavaScript should not be judged as if it was a poor version of the other popular languages - it isn't a Java or a C++ clone. It does things its own way.  In particular, it doesn't do inheritance  [ ... ]


Other Articles

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

espbook

 

Comments




or email your comment to: comments@i-programmer.info

Banner


JavaScript Canvas - Fetch API

Working with lower-level data is very much part of graphics. This extract from Ian Elliot's book on JavaScript Graphics looks at how to use typed arrays to access graphic data.



JavaScript Jems - The Inheritance Tax

JavaScript should not be judged as if it was a poor version of the other popular languages - it isn't a Java or a C++ clone. It does things its own way.  In particular, it doesn't do inheritance  [ ... ]


Other Articles

 



Last Updated ( Sunday, 19 February 2017 )