curl/tdd/runner is a bit complicated atm. Just thinking of something that might be a bit simpler:
curl(['curl/tdd/isolate'], function (isolate) {
// inject AMD `require` and `define`, as well as a "done" callback.
// the test function is guaranteed to run in isolation and all modules
// are undefined afterward.
isolate(function test (require, define, done) {
// define mocks:
define('pkg/mod1', { foo: 42 });
define('pkg/mod2', ['pkg/dep1'], function (dep1) {
return {
bar: function (val) { return String(dep1(val)); }
}
});
// fetch the module to test and any unmocked modules
require(['pkg/unit/to/test'], function (unitToTest) {
// tests go here
assert.equals('a string', unitToTest.method('a string'));
unitToTest.asyncThing(function (val) {
// more tests
assert.true(val);
done();
})
});
// hmmm. anything here will exec before require callback and may
// cause confusion?
});
});
Here's another possible API that removes the uncertainty of code around the async require:
curl(['curl/tdd/isolate'], function (isolate) {
// the test function is run in isolation and the mocker function is run
// immediately prior. all modules are undefined afterward.
isolate(
['pkg/unit/to/test', 'other/unmocked/thing'],
function mocker (define) {
// define mocks:
define('pkg/mod1', { foo: 42 });
define('pkg/mod2', ['pkg/dep1'], function (dep1) {
return {
bar: function (val) { return String(dep1(val)); }
}
});
},
function test (unitToTest, otherThing, /* yuk: extra param */ done) {
// tests go here
assert.equals('a string', unitToTest.method('a string'));
unitToTest.asyncThing(function (val) {
assert.true(val);
done();
})
}
);
});
It should be pretty easy to make it configurable:
// example config to use curl/tdd/isolate with requirejs
isolate.config({
require: requirejs,
define: define, // redundant
undefine: requirejs.undefine
});
// setup and teardown ???
isolate.config({
setup: mySetupFunction, // runs before each mocker function
teardown: myteardownFunction // runs after each test
});
Since js is single-threaded, we could obtain a done function inside the test:
function test (unitToTest, otherThing) {
var done = isolate.waitFor('my test'); // get a named "done" function
// tests go here
assert.equals('a string', unitToTest.method('a string'));
unitToTest.asyncThing(function (val) {
assert.true(val);
done();
})
}
I like the look of the commonjs-style sync require() version. I think I could still get that to work with other AMD loaders, too. Behind the scenes, it'd have to do something like this to invoke the "AMD-wrapped CommonJS" rules in a cross-loader way:
define('some-unique-name' + counter++, isolatedTestFunc);