I checked in the basic structure on Git Hub. The code is untested (I extracted it from our current project), but we’ll go over the code in this blog. See QUnit Acceptance Test Framework Repository on Git Hub
The QUnit Test Page
GitHub – index.html
As many developers are already familiar with, QUnit runs the tests through a browser from a HTML page. We have a QUnit test page for every web application, which we separate by directories for scalability. On Git Hub, I named this directory “app,” but it doesn’t have to be separated if there is only one application.
The index file contains an iframe with id “myFrame,” which is where we will load our web application for testing.
All of our test scripts are loaded using Require JS. We use this library mainly for organization. Line 7 is using Require JS to load our main configuration file config.js.
The Main Configuration Page
GitHub – config.js
The main configuration page is for Require JS. It’s loading jQuery and Underscore, then calling our next file setup.js, which is our setup file for the tests. This is a very simple configuration file and can easily be extended. I thought this would be more useful, but the test framework itself doesn’t need many libraries. I think we need to inject libraries into the webapp’s window for better use (see jquery.simulate example below).
GitHub – setup.js
The test setup sets up the application’s tests. It’s a standard Require JS file that takes in jQuery as a dependency. It saves the jQuery dependency as $$ (line 6). This is to allow the $ variable to point to the iFrame’s jQuery (line 16). We want to perform our test validations against the iFrame’s jQuery, so we’re assigning the iFrame’s jQuery as the standard jQuery variable $.
We import our test suites using the require command. This will look for suite1.js. We can easily add additional require lines for suite2.js, suite3.js, etc.
You’ll notice that we have this code in the iFrame’s load event. This is to refresh all the configurations every time the iFrame loads (on page redirects). We also have QUnit start, which should help us time the tests (not tested thoroughly).
The configurations in the setup are to facilitate test execution. We turn off async (line 19), inject jquery simulate (library to simulate a real click) to the content window (line 23), turn off animations (line 26-27).
Since the iFrame is loaded and the configurations are loaded, we start QUnit.
GitHub – suite1.js
The test suites are files with the actual QUnit tests. We define a referencePath to avoid cross domain scripting – it makes the test framework agnostic to localhost vs. 127.0.0.1, etc.
This suite has one module that contains one test. The setup and teardown methods are there to show how testing with page redirection works. To explicitly change the URL manually, we use the parent jQuery object ($$) then change the attr of src (line 9). This shouldn’t happen very often as most redirections will be via button clicks in the web application themselves.
You’ll notice that we have two stop() calls on each page redirection. This is because a page redirection triggers the iFrame’s load event, which has a start() call. By having two stop calls, we’re able to ensure the test occurs after the timeout and after the page load. This hasn’t been thoroughly tested yet, but in theory, it seems okay for now.
The GitHub project contains some miscellaneous files like grunt.js and package.json. This is for running the framework via Grunt/Node JS/Phantom JS. We’ve been using this stack for a few months and noticed discrepancies in results between operating systems, phantom JS versions, and browsers. We’re mitigating this issue by having the continuous integration environment run in the same type of environment as the developer machines.
Please let me know if you are using a similar framework as we can share tips and gotchas.
+ When working with async tests, have one QUnit.stop() in the beginning of the test (unless you call asyncTest, which includes this feature) and call one QUnit.start() at the end of the test. The examples above with multiple stops and starts are not correct.
+ After some testing, we started using the async.series library over the setTimeout. This practice organizes the tests better and allows for better event usage. Use events like animationComplete, load, etc.
+ In cases where events only get you half way, sometimes a loop that waits for the condition is a workaround. Our iFrame’s load event triggered before the page was actually loaded, so we added a loop to check for the jquery object before configuring jquery.