Home / Blog / qunit /

QUnitAdapter 1.1.0

April 9th, 2010

I’ve updated the JS Test Driver QUnitAdapter to improve compatibility with QUnit.

Variables set on the this object within are now available within setup, teardown, and the tests themselves.

module('Lifecycle', {
  setup: function() {
    this.mockString = "some string";
  },
  teardown: function() {
    equals(this.mockString, "some string");
  }
});
 
test("Things assigned to this in setup are available in test", function() {
  equals(this.mockString, "some string");
});

The test function now supports the optional second parameter of the expected number of assertions.

// declare that this test has expects 1 assertion
test('Test with expected defined as 2nd param', 1, function(){
  ok(true);
});

My thanks go to anotherhero for providing the patch to fix both these issues.

You can always download the latest version of QUnitAdapter from Google Code.

QUnitAdapter 1.0.3

November 10th, 2009

Just a small update to the JS Test Driver QUnitAdapter. Version 1.0.3 has been released, and fixes a single bug:

Issue 64: QUnit Adapter fails to run tests if you don’t include a module

You can now declare tests without a module, and they will run under the Default Module. In previous versions these tests would be silently ignored (whoops!).

You can always download the latest version of QUnitAdapter from Google Code.

QUnit to JSpec Adapter

September 3rd, 2009

JSpec is a Javascript BDD framework with a lot of great things going for it: It can run without a browser (great for continuous integration servers), it has a Ruby style custom syntax which makes tests easier to write and read, and it uses a BDD style describe/should syntax.

It’s a very tempting framework to use, but I already have a large collection of tests using qunit. I don’t want to use two frameworks for one project, and I don’t want to rewrite 300+ tests, so what to do?

How about a QUnit to JSpec Adapter, in the vein of my QUnit to JS Test Driver Adapter. Just load the adapter into JSpec as a normal javascript file, and you can nowexec() qunit test files in JSpec.

Downloading the QUnit to JSpec Adapter

First up download the QUnit to JSpec Adapter, or copy the code below, and save it somewhere in your project (e.g. a lib folder).

QUnitToJSpecAdapter.js

/*
QUnitToJSpecAdapter
Version: 1.0.0
 
Run qunit tests using JSspec
 
This provides almost the same api as qunit.
 
Tests must run sychronously, which means no use of stop and start methods.
You can use the JSpec mock timers to deal with timeouts, intervals, etc
 
The qunit #main DOM element is not included. If you need to do any DOM manipulation
you need to set it up and tear it down in each test.
 
*/
(function() {
 
    JSpec.addMatchers({
        be_ok : '!!actual'
    });
 
 
    JSpec.context = JSpec.defaultContext;
    JSpec.context.QUnitAdapter = {
        modules: []
    };
 
    module = function(name, lifecycle) {
 
        JSpec.context.QUnitAdapter.modules.push({
            name: name,
            tests: [],
            setup:    (lifecycle && lifecycle.setup)    ? lifecycle.setup    : function() {}, 
            teardown: (lifecycle && lifecycle.teardown) ? lifecycle.teardown : function() {}
        });
 
        JSpec.describe(name, function() {
 
            var length = QUnitAdapter.modules[0].tests.length;
            for (var i = 0; i < length; i++) {
                it(QUnitAdapter.modules[0].tests[i].name, function() {
                    var adapter = {
                        expectedAsserts: 0,
                        calledAsserts: 0,
 
                        expect: function(count) {
                            adapter.expectedAsserts = count;
                        },
 
                        ok: function(actual, msg) {
                            adapter.calledAsserts++;
                            JSpec.expect(actual).to(JSpec.matchers.be_ok);
                        },
 
                        equals: function(a, b, msg) {
                            adapter.calledAsserts++;
                            JSpec.expect(a).to(JSpec.matchers.be, b);
                        },
 
                        start: function() {
                            throw 'start and stop methods are not available when using JSpec.\n' +
                                'Use the JSpec timer to deal with timeouts and intervals:\n' + 
                                'http://github.com/visionmedia/jspec/tree/master';
                        },
 
                        stop: function() {
                            throw 'start and stop methods are not available when using JSpec.\n' +
                                'Use the JSpec timer to deal with timeouts and intervals:\n' + 
                                'http://github.com/visionmedia/jspec/tree/master';
                        },
 
                        same: function(a, b, msg) {
                            adapter.calledAsserts++;
                            JSpec.expect(a).to(JSpec.matchers.eql, b);
                        },
 
                        reset: function() {
                            throw 'reset method is not available when using JSpec';
                        },
 
                        isLocal: function() {
                            return false;
                        }
                    };
 
                    eval('with(adapter) {' +
                        JSpec.contentsOf(QUnitAdapter.modules[0].setup) +
                        'try {' +
                        JSpec.contentsOf(QUnitAdapter.modules[0].tests[0].testCallback) +
                        '} catch(ex) { throw(ex); } finally {' +
                        JSpec.contentsOf(QUnitAdapter.modules[0].teardown) +
                        '} }');
 
                    if (adapter.expectedAsserts > 0) {
                        JSpec.expect(adapter.calledAsserts).to(JSpec.matchers.equal, adapter.expectedAsserts);
                    }
                });
            }
 
            after_each(function() {
                QUnitAdapter.modules[0].tests.shift();
            });
 
            after(function() {
                QUnitAdapter.modules.shift();
            });
 
        });
 
    };
 
    test = function(name, testCallback) {
        JSpec.context.QUnitAdapter.modules[JSpec.context.QUnitAdapter.modules.length - 1].tests.push({
            name: name,
            testCallback: testCallback
        });
    };
 
})();

Having some QUnit tests

First up you need some qunit tests. Having the qunit test files in the spec directory helps simplify loading them.

As an example you can use the following:

qunit-tests.js

module('Examples');
 
test('True is ok', function() {
  expect(1);
  ok(true);
});
 
module('Examples with lifecycle', {
  setup: function() {
    started = 'yes';
  },
  teardown: function() {
    ok(started);
    started = null;
  }
});
 
test('Test has started', function() {
  expect(2);
  equals(started, 'yes');
});

Configuring QUnit Support

Then to enable QUnit tests to be run in JSpec, you must have JSpec load the adapater as a normal javascript file, andexec() the QUnit test file as you would a JSpec spec file.

For JSpec rhino support your spec.rhino.js file would look like:

spec.rhino.js

load('/Library/Ruby/Gems/1.8/gems/visionmedia-jspec-2.10.0/lib/jspec.js')
load('lib/QUnitAdapter.js')
 
JSpec
.exec('spec/qunit-tests.js')
.run({ formatter : JSpec.formatters.Terminal })
.report()

Notice how the QUnit test file is exec’d exactly as you would a normal spec file. You can run specs and Qunit tests along side each other without any interference.

And for running the tests within a browser your spec.dom.html file would look like:

spec.dom.html

<html>
    <head>
        <link type="text/css" rel="stylesheet" href="/Library/Ruby/Gems/1.8/gems/visionmedia-jspec-2.2.1/lib/jspec.css" />
        <script src="/Library/Ruby/Gems/1.8/gems/visionmedia-jspec-2.2.1/lib/jspec.js"></script>
        <script src="../lib/QUnitAdapter.js"></script>
        <script>
            function runSuites() {
                JSpec
                .exec('qunit-tests.js')
                .run()
                .report()
            }
        </script>
    </head>
    <body class="jspec" onLoad="runSuites();">
        <div id="jspec-top"><h2 id="jspec-title">JSpec <em><script>document.write(JSpec.version)</script></em></h2></div>
        <div id="jspec"></div>
        <div id="jspec-bottom"></div>
    </body>
</html>

Running the tests

To run the tests just launch JSpec as normal. My prefered method is to run the tests using rhino, to do this navigate to your project root directory in a terminal window and run:

jspec run --rhino

And you should now see your QUnit tests running in JSpec:

 Passes: 5 Failures: 0
 
 Examples
  True is ok..
 
 Examples with lifecycle
  Test has started...

Limitations

This adapter has many of the same limitations and my QUnit to JS Test Driver adapter.

The tests must run synchronously (which means no use of the qunit stop and start methods).

If you need to test timeouts, intervals, or other asynchronous sections of code , you can use themock timers that come with JSpec.

QUnit DOM support is not included. Consider avoiding interacting directly with the browser within your unit tests. But if you do need to, you’ll need to create and remove the DOM objects yourself with each test, or the setup and teardown methods.

And lastly, tests are broken out of any closures before they run. This means they lose access to any closure variables. For example, the follow test would work in QUnit, but not in JSpec. When running in JSpec access to thesetup variable is lost.

(function(){
  var setup = function() {
    // do setup...
  };
 
  test('name', function() {
    setup();
  });
})();

QUnit Adapter 1.0.2

August 30th, 2009

A new version of the JS Test Driver QUnit Adapter is available.

Version 1.0.2 fixes a small bug where a module lifecycle object without Setup or Teardown methods would cause a test to error. For example:

module('Lifecycle', {});
 
test('', function() {
	expect(1);
	ok(true, 'tests still run successfully even if Setup and Teardown are undefined');
});

Would give the error Lifecycle.test error (1.00 ms): Result of expression 'l.setUp' [undefined] is not a function..

This is now fixed so the test behaves as if no lifecycle was defined.

You can get the new 1.0.2 verison of the QUnit Adapter from Google Code.

Update to QUnit Adapter

July 2nd, 2009

I’ve made a small update to the QUnit Adapter for JS Test Driver.

This fixes a bug where the ok() assertion was much stricter than the qunit equivalent. It was only succeeding when passed a boolean true value.

Now it behaves the same as the qunit version, and succeeds with all values other than 0, false, or null.

You can get the new 1.0.1 verison of the QUnit Adapter from Google Code.

In my last post I introduced a QUnit to JS Test Driver adapter, a small adapter which converts QUnit tests into native JS Test Driver tests.

This adapter is now part of the JS Test Driver project.

There is now a QUnit Adapter wiki page on the JS Test Driver project site. This will be updated to keep up with any changes to the adapter.

The code is now stored within the JS Test Driver subversion repository. You can always download the latest version of the QUnit Adapter using the Google Code web interface.

Thanks to Jeremie Lenfant-engelmann for deeming QUnit Adapter worthy of official regonition :)

The code in this post is out of date. The latest code can always be found in the JS Test Driver google code site

In my previous post on QUnit and JS Test Driver I showed how to run your qunit tests with JS Test Driver.

The technique used was to run the tests with qunit, and report either their success or failure to JS Test Driver. This works, but you miss out on the important feedback of exactly which assertions have failed, and why.

This problem has led me to taking a new approach to running qunit tests with JS Test Driver, where I don’t use any of the existing qunit code, and instead just create an interface wrapper that converts qunit style tests and assertions directly into JS Test Driver tests and assertions.

This gives assertion level error reporting, making it much easier to write and debug tests. Essentially this adapter allows you to write native JS Test Driver tests, but using the less verbose qunit syntax.

The new approach also means that qunit lifecycles (setup and teardown) work.

Installing the QUnit Adapter

First up download the equiv.js file, which is required for the qunit same assertion.

Then download the QUnitAdapter.js file (or copy the code below).

QUnitAdapter.js

(function() {
 
    window.module = function(name, lifecycle) {
        QUnitTestCase = TestCase(name);
 
        if (lifecycle) {
            QUnitTestCase.prototype.setUp = lifecycle.setup;
            QUnitTestCase.prototype.tearDown = lifecycle.teardown;
        }
    };
 
    window.test = function(name, test) {
        QUnitTestCase.prototype['test ' + name] = test;
    };
 
    window.expect = function(count) {
        expectAsserts(count);
    };
 
    window.ok = function(actual, msg) {
        assertTrue(msg ? msg : '', actual);
    };
 
    window.equals = function(a, b, msg) {
        assertEquals(msg ? msg : '', b, a);
    };
 
    window.start = window.stop = function() {
        fail('start and stop methods are not available when using JS Test Driver.\n' +
            'Use jsUnit Clock object to deal with timeouts and intervals:\n' + 
            'http://googletesting.blogspot.com/2007/03/javascript-simulating-time-in-jsunit.html.');
    };
 
    window.same = function(a, b, msg) {
        assertTrue(msg ? msg : '', window.equiv(b, a));
    };
 
    window.reset = function() {
    	fail('reset method is not available when using JS Test Driver');
    };
 
    window.isLocal = function() {
    	return false;
    };
 
    window.QUnit = {
    	equiv: window.equiv,
    	ok: window.ok
    };
 
})();

Save both these files to your project (for example tests/qunit/).

Configuring JS Test Driver

To run your qunit tests in JS Test Driver you need to configure it to load the adapter before your qunit tests.

Update your jsTestDriver.conf to load the files:

server: http://localhost:9876
 
load:
  # Add these lines to load the equiv function and adapter in order, before the tests
  # (assuming they are saved to tests/qunit/)
  - tests/qunit/equiv.js
  - tests/qunit/QUnitAdapter.js
 
  # This is where we load the qunit tests
  - tests/js/*.js
 
  # And this loads the source files we are testing
  - src/js/*.js

Running JS Test Driver with qunit tests

Now we can run JS Test Driver and watch as it runs all our qunit tests!

The tests will run as individual JS Test Driver tests, with the format Module Name.Test Name.

Example output:

[PASSED] Module 1.test Test 1
[PASSED] Module 1.test Test 2
[PASSED] Module 2.test Test 1
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (1.00 ms)
  Safari 530.18: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (1.00 ms)

Limitations

There are a few limitations on which qunit tests will successfully be converted.

The tests must run synchronously (which means no use of the qunit stop and start methods).

If you need to test timeouts, intervals, or other asynchronous sections of code, consider using the jsUnit Clock object to deal with timeouts and intervals.

QUnit DOM support is not included. Consider avoiding interacting directly with the browser within your unit tests. But if you do need to, you’ll need to create and remove the DOM objects yourself with each test, or the setup and teardown methods.

QUnit and JS Test Driver

June 21st, 2009

This post has obsoleted been the new QUnit Adapter I created, check it out!

I was very impressed by the new Google JS Test Driver project, which provides a blisteringly fast, and easily automated way of running your Javascript unit tests. See this introduction to JS Test Driver by Miško Hevery for a great overview.

I previously described how to run JS Test Driver automatically with Autotest.

But I have an existing project that uses the jQuery testing framework qunit for testing. I didn’t really fancy rewriting 300+ tests just so I could use the JS Test Driver framework.

So I wrote a converter that automatically converts qunit modules and tests into JS Test Driver TestCases and test methods.

Download Converter and Patched Testrunner

In order to convert from qunit tests I’ve had to add a few extra hooks into the qunit testrunner.js file.

Either download the patched testrunner.js file, or just add the 3 lines below:

234
235
236
237
238
239
240
241
242
243
244
245
    QUnit: {
        // Add the following 3 lines 
        runTest: runTest,
        config: config,
        validTest: validTest,	
 
        // This is existing code	
        equiv: equiv,
        ok: ok,
        done: function(failures, total){},
        log: function(result, message){}
    },

Next download QUnitToTestCases.js and save it to the same folder as testrunner.js. This is the file which converts the qunit tests into TestCases that JS Test Driver understands.

It works by overriding the qunit test() function, and rather than adding the test to qunit, it creates a test method on a TestCase object which, when called by JS Test Driver adds the test to qunit and runs it.

Configuring JS Test Driver

Once you have the patched testrunner.js and QUnitToTestCases.js, you just need to let JS Test Driver know to load them before your qunit tests. They need to be loaded in order, with testrunner.js first, followed by QUnitTiTestCases.js, as the converter modifies some of the testrunner methods.

Update your jsTestDriver.conf to load the files:

server: http://localhost:9876
 
load:
  # Add these lines to load the testrunner and converter in order, before the tests
  # (assuming the files are saved to tests/qunit/)
  - tests/qunit/testrunner.js
  - tests/qunit/QUnitToTestCases.js
 
  # This is where we load the qunit tests
  - tests/js/*.js
 
  # And this loads the source files we are testing
  - src/js/*.js

Running JS Test Driver with qunit tests

Now we can run JS Test Driver and watch as it runs all our qunit tests!

The tests will run as individual JS Test Driver tests, with the format Module Name.Test Name.

Example output:

[PASSED] Module 1.test Test 1
[PASSED] Module 1.test Test 2
[PASSED] Module 2.test Test 1
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (1.00 ms)
  Safari 530.18: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (1.00 ms)

Limitations

There are a few limitations on which qunit tests will successfully be converted.

The tests must run synchronously (which means no use of the qunit stop and start methods).

Module lifecycles are ignored at the moment, which means setup and teardown functions are not called.