A quick introduction to JavaScript unit testing with QUnit.js for complete beginners!
If you are new to Web/Mobile Application development, you might overlook the (many) benefits of (automated) testing (please star/bookmark this page so you can come back to it later) ... we still encourage everyone to learn about testing and have made every effort to keep this tutorial accessible to beginners (simple code/examples), but acknowledge this is a bit "theoretical" until you have felt the pain of having an app "breaking" due to insufficient testing ...
Most of us have at least attempted to learn a musical instrument.
Learning Test Driven Development (TDD) is like learning/practicing "scales"
when learning to play a musical instrument.
It can seem like a "waste of time" to a young novice musician who only
wants to learn enough to play their favourite song, but by learning/practicing sequences of notes in quick succession, we are able to unlock a whole
other level of musical excellence.
Persistence pays handsomly. (Automated) Testing is one of the fundamental skills of software development. Embrace it if you want to unlock the next level in your creativity.
Consider what it takes to build even a modest physical building. Anything over a single level requires much consideration and planning. If you are lucky enough to live in a country with well established building regulations the following sight will be unfamiliar to you:
When a building is constructed by inexperienced people, they often "forget" to dig/lay a foundation. This failure will only be visible months/years later (usually after the cowboy builders have made off with the cash). (Automated) tests are the foundation of great software. Competent civil engineers would not dream of constructing a building without a foundation, as software engineers we should have the same approach.
The confidence that comes when building upon a solid foundation only arises if we put in the time to first dig a foundation.
Thinking about how you will test your solution to a problem, changes the way you perceive the problem. It might seem initially counter-intuitive to think about the test before you have solved the problem, but we urge you to consider the perspective of a car designer:
We don't like thinking about car accidents (they are brutal and often fatal!) but a great car designer will always consider the "worst case scenario" for their vehicle and design with that eventuality in mind.
Sure, the fast convertible is nice to look at, but if you had to pick a car for your loved ones, would it be sports car or the Volvo with the dedicated team of life-saving expert engineers.
Put your lab coat on and embrace the discipline of proactively thinking about how to test your creations!
Wether novice or experienced, (automated) testing is the foundation we need to build great apps.
If you have already built a few apps and felt the pain of having to manually test (and re-test) in several browsers, this screenshot will look like nirvana to you:
These are the Continuous Integration (CI) Tests for QUnit.
Each time a commit is saved the entire suite of (automated) tests is run in
all modern browsers automatically to confirm that everything still works
as expected.
The official description on http://qunitjs.com/ is:
QUnit is a powerful, easy-to-use JavaScript unit testing framework.
It's used by the jQuery, jQuery UI and jQuery Mobile projects
and is capable of testing any generic JavaScript code.
Our Top Five reasons you should learn QUnit are:
- Shallow learning curve. (start testing in 5 mins!)
- See your tests in the Browser (Nothing to Install or Configure! ... also works in Node.js server code!)
- Great Documentation (see Useful Links below)
- Well established and used extensively by JQuery developers (so you know it works everywhere!)
- Great Ecosystem! (QMock, TestSwarm & Blanket.js -> client-side code coverage)
Try it: https://qunit.herokuapp.com/?coverage=true
Yes, most of these reasons (for learning QUnit) are also applicable
to Mocha and Jasmine.
I'm not advocating one testing framework over another.
I've used Mocha JS quite a bit in the past and wrote a
Learn Mocha tutorial
and I used Jasmine extensively
@MakePositive
... I actually suggest you make time to learn a few
JS testing frameworks and then pick the one you like best!
git clone https://github.com/nelsonic/learn-qunit.git
Open the learn-qunit directory and have a look around. The main file you need to inspect is ./test/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Learn QUnit</title>
<link rel="stylesheet" href="resources/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="resources/qunit.js"></script>
<script src="test/tests.js"></script>
</body>
</html>
(it references two JavaScript files qunit.js which is in the ./resources directory and test.js which is in the ./test directory)
In the body of the index.html file there are two div elements with ids of qunit and qunit-fixture these are where QUnit will
(it references two JavaScript files qunit.js and test.js both these files are in the ./resources directory)
In the body of the index.html file there are two div elements with ids of quint and qunit-fixture these are where QUnit will display the results of our unit tests.
In a previous tutorial I built a simple stopwatch: https://github.com/nelsonic/stopwatch but its deliberately "minimalist" (did not have tests and all code was contained in a single html file) This time we are going to do it the "right" way, then you can compare.
- Counter should be at Zero when we start
- Start Counting time from a specific point
- Stop Counting
- Continue Counting (without resetting) pick up where we left off.
- Re-set the counter to Zero.
- /lib contains the stopwatch.js module file
- /test contains the tests.js file with all our tests and index.html which is our QUnit "test runner" html file.
Note: To facilitate offline learning I've included qunit.js and qunit.css in the /resources directory, but on a "real" project you should use the CDN versions (see http://qunitjs.com/ bottom of the homepage for latest links.)
Unit tests in QUnit are insanely simple as you are about to see!
We expect a Timer/Stopwatch to be Zero before we start it.
(that's a lot of zeros! one would be enough.)
Lets write a test for that:
test( "timeElapsed should be Zero before we start the Timer", function() {
equal( T.timeElapsed, 0, true );
});
We pass what we're looking to test in as the first parameter to the equal assertion and our expected result in as the second parameter. This test assumes we have a T (Timer) Module. The T module should have a variable called timerStarted which should be 0 (zero) before we start the timer.
For more tests see: ./test/tests.js
Run the tests by opening index.html. Our first fail is because we do not have a variable called "T":
First we create the T (Timer) Module and our two main variables timeElapsed and **timeStarted
var T = (function () { // create a basic module ("IIFE") for our Timer
'use strict';
var timeElapsed = 0, // number of miliseconds since timerStarted
timeStarted = 0; // timestamp when timer was started
// allow external access to private variables & methods by returning them:
return {
timeStarted: timeStarted,
timeElapsed: timeElapsed
};
}());
If this "wrapped" JavaScript function looks strange to you,
read this:
http://en.wikipedia.org/wiki/Immediately-invoked_function_expression
Now our first unit test passes:
The next unit test we need to write is to start our timer:
test( "startTimer() starts counting from *NOW* (when instructed)", function() {
var startTime = new Date().getTime();
equal( T.startTimer(startTime), startTime, true );
});
All this does is checks the timer started when we asked it to.
// Initialize the application
var startTimer = function (startTime) {
timerStarted = startTime; // argument externally supplied
console.log("startTime: "+startTime);
return timerStarted;
};
That does just enough to pass the test.
Once you have your process nailed:
- Write a test and watch it fail
- Write just enough code to pass the test (without breaking any other test that was already passing!)
You can go through all the requirements for the stopwatch and grow your application one feature at a time.
Once you have a full batch of passing tests you can relase the app!
Following the Blanket.js Getting Started Guide
- (Download and) Add blanket.js file to ./resources
- Referecent blanket.js script in index.html (our test runner) below qunit.
- Add the data-cover atribute to the
<script>
we are testing<script src="lib/stopwatch.js" data-cover></script>
- Re-run the test runner (refresh the index.html page)
- Enable coverage checkbox and refresh again.
You should now see the test coverage for the project!
Note: for some reason this was giving an error in Google Chrome...
This appears to be a known issue:
http://stackoverflow.com/questions/14481029/how-to-stop-global-failures-in-qunit
I tried adding JQuery to index.html but still get the same error in Chrome.
Further investigation in the Chrome Developer Console reveals the following error detail:
- Cross origin requests are only supported for HTTP.
- Uncaught NetworkError: A network error occurred. [blanket.js:5317]
This error is due to accessing index.html on localhost. When I put the files on S3 it works flawlessly in all browsers. https://qunit.herokuapp.com/?coverage=true
- QUnit intro tutorial: http://qunitjs.com/intro/
- QUnit on GitHub: https://github.com/jquery/qunit
- QUnit API Docs: http://api.qunitjs.com/category/all/
- QUnit Cookbook (plenty of examples!): http://qunitjs.com/cookbook/
- QUnit Assertions (other than
equal()
): http://api.qunitjs.com/category/assert/ - Blanket.js Test Coverage: http://blanketjs.org/
- JQuery's TestSwarm: http://swarm.jquery.org/
- QUnit "Before Each" (workaround): http://stackoverflow.com/questions/1683416/how-do-i-run-a-function-before-each-test-when-using-qunit
- QUnit with Sinon (Backbone): http://addyosmani.com/blog/unit-testing-backbone-js-apps-with-qunit-and-sinonjs/
Yes, we know that most of the reasons (for learning QUnit) described above are also applicable to Mocha, Jasmine, etc.
We are not saying one test framework is "better" than the other.
In the past 5 years we have made a point of using all the JavaScript testing frameworks we wrote a popular
Mocha.js Tutorial
... we actually suggest you make time to learn a few
JS testing frameworks and then pick the one that suits your needs!
Next: PhantomJS with QUnit: http://www.ianlewis.org/en/phantom-qunit-test-runner