Injecting Stubs/Mocks into Tests with Require.js
By Adrian Sutton
I’ve recently embarked on a fairly complex new application, a large part of which is a webapp written in JavaScript. The application uses require.js to handle modules and loading of dependencies and we want to be able to unit test our JavaScript.
In order to test specific pieces of the application, we want to be able to inject stubs or mocks into the module being tested. For example, if we had a module:
define(['dataSource', 'utils'], function(dataSource, utils) {...});
We might want to load the normal ‘utils’ module but stub out the ‘dataSource’ module to hard code data instead of retrieving it from the server. You can do this across all tests using the require.js config:
require.config = {
paths: { 'dataSource': 'stubDataSource' }
}
Which causes require.js to always load stubDataSource.js when ‘dataSource’ is requested. This is simple to do, but means every test gets the stub data source and there’s no way to test dataSource itself.
Fortunately, require.js provides the ability to unload a module using require.undef and to define a module with an explicit name. We can combine these functions to override any module with a custom implementation temporarily. I define a test utils module that makes this simple for tests to use:
define(['require'], function(require) {
var stubbed = [];
return { stub: function(name, implementation) { stubbed.push(name); requirejs.undef(name); define(name, [], function() { return implementation; }); }, loadWithCurrentStubs: function(name, callback) { stubbed.push(name); requirejs.undef(name); require([name], callback); }, reset: function() { stubbed.forEach(function(name) { requirejs.undef(name); }); stubbed = []; } };
});
Which would be used like (typically utilising a test framework like mocha):
define(['testUtils'], function(testUtils) {
testUtils.stub('dataSource', {...});
testUtils.loadWithCurrentStubs('moduleToTest', function(moduleToTest) {
// Run tests
testUtils.reset();
});
});
For each stub, we first undef any real module that may have been loaded, then use the named version of define to inject our stub into require.js’ cache of loaded modules.
Then once all the required stubs are loaded, we force the module we want to test to be reloaded, otherwise require.js may have a cached version of it with its real dependencies already injected. Unfortunately that reload has to be asynchronous which adds a little complexity but any good test framework can handle that. For example with mocha the setup would be:
define(['testUtils'], function(testUtils) {
var moduleToTest
beforeEach(function(done) {
testUtils.stub('dataSource', {...});
testUtils.loadWithCurrentStubs('moduleToTest', function(loadedModule) {
moduleToTest = loadedModule;
done();
});
});
afterEach(function() {
testUtils.reset();
});
// Tests...
});
Once we’ve run our tests we simply undef any modules we’ve stubbed or that have been reloaded to use those stubs. Ideally we’d undef everything require.js has loaded to be completely sure our stubs haven’t leaked into any other modules, but I haven’t yet found a way to do that with require.js.