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 --rhinoAnd 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.
QUnitAdapter now part of JS Test Driver
July 1st, 2009
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 :)
New QUnit to JS Test Driver adapter
June 23rd, 2009
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
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.