Just a while ago I checked out QUnit. I fiddled around with it, wrote some simple tests and I thought, this could be very useful. When writing code you always add some new features into existing functionality and that’s where the errors and regressions appear, especially when it gets complex.
As we know, unit testing is great for frameworks with a lot of different functions depending on each other (that’s one reason the jQuery-team developed or uses QUnit for testing). But how about testing user interfaces or simulating user interaction on websites? On websites we sometimes use small bits of code responding to the interaction or the input of a user, for example hiding or toggling elements or dynamicaly change content. When we modify parts of the code we don’t want to click every button to test if everything is responding normal.
There is one way to test parts of your interface without modifing your existing code (I don’t know if this is the best way but it’s one way to run tests in a simple way).
1. Setup your test environment
When you download QUnit, it comes with a basic test environment we can use. It looks something like that:
[...]
<link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
<script type="text/javascript" src="../qunit/qunit.js"></script>
[...]
<h1 id="qunit-header">QUnit Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>
<div id="qunit-fixture">test markup</div>
[...]
2. Modify the test envoironment for our use
First, I added jQuery to our test envoironment, mainly to use the DOM ready provided by jQuery. Second, I added an iframe where the interface/website we want to test is placed:
[...]
<link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
<!-- add jQuery for the testing environment -->
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="../qunit/qunit.js"></script>
[...]
<h1 id="qunit-header">QUnit Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>
<div id="qunit-fixture">test markup</div>
<iframe src="interface-we-want-to-test.html" height="240" width="320"></iframe>
[...]
3. Get the jQuery-Object from your original code
After the DOM ready we have two different jQuery objects in the browser. The one from the test environment and our original object in the iframe. To avoid any trouble we „cleanup“ the standard $ object in our test environment and get a new jQuery object in noConflict mode named „$q“ (for QUnit);
[...]
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="../qunit/qunit.js"></script>
<script type="text/javascript" charset="utf-8">
var $q = jQuery.noConflict(true),
$ = null,
jQuery = null;
</script>
[...]
4. Get the „original“ jQuery object
The next step is to get the jQuery object from the iframe to trigger events like clicks, hovers or submits in your interface. We can do this after the DOM ready of the test environment:
[...]
<div id="qunit-fixture">test markup</div>
<iframe src="interface-we-want-to-test.html" height="240" width="320"></iframe>
</body>
<script>
// on DOM ready
$q(function() { // fire event when iframe is ready
$q('#testframe').load(function() {
// Get the jQuery Object from the original code
$ = window.frames[0].jQuery;
});
});
</script>
[...]
5. Let’s test
After the setup we can run a simple test. This is very basic…just an example.
[...]
// on DOM ready
$q(function() {
// fire event when iframe is ready
$q('#testframe').load(function() {
// Get the jQuery Object from the original code
$ = window.frames[0].jQuery;
test('check if dialog closes', 3, function() {
// On start this is visible
ok($('#dialog').is(':visible'));
// Simulate a click
$('#close-dialog').click();
// Now #dialog should be hidden...
ok(!$('#dialog').is(':visible'));
// Click again
$('#close-dialog').click();
// #dialog should still be hidden...
ok(!$('#dialog').is(':visible'));
});
});
});
[...]
Though, this example isn’t very complex or logical but if we change (for some unknown reasons) our original code from:
$('#close-dialog').click(function() { $('#dialog').hide(); });
to
$('#close-dialog').click(function() { $('#dialog').toggle(); });
our test will fail, because after the second click the dialog would be visible again.
Some other test checking the error validation of a ajax form could look like this:
test('check if form throws error on email validation', function() {
// Write some invalid values to the email field
$('form input#email').val('somewrong.email.com');
// submit the form
$('form').submit();
// check if the input field gets an error
ok($('input#email').hasClass('error'));
});
Conclusion
I don’t know how complex the tests can get and I’m shure you can’t test everything but this is one aproach to test interface functions and avoid regressions without clicking through your interface and checking every response. Of course, this is just for testing the code and the response of the interface and not the usability. (This stuff was just tested in Google Chrome, no warranty for other browsers)
Download example or check the the GitHub Repository
Note: Make sure you run this on a server (or your local server) otherwise you will have problems accessing the iframe via JavaScript.
UPDATE:
- 2012/02/25: Added a download link with a „working demo“ after Jesse Breuer made the proposal in the comments.