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();
})
}
Hmmm, yeah, I like things about each one, but I think I like the 2nd better. What if we reverse the
test()
andmocker()
functions, making the test the most visible and obvious thing, and making it easy for someone to test without supplying amocker
?Just brainstorming out loud here:
I don't like that there are 2 requires in that example (just figured I'd try it--it doesn't have to be written that way), but hanging
done()
off of the inner require seems potentially interesting.How will the module ids given to
define()
inmocker
play with AMD path/package mappings? Will path/package mappings even be used in tdd at all? Will users need a 2nd set of "override" path/package mappings?