Reputation: 20184
I have an AMD module I want to test, but I want to mock out its dependencies instead of loading the actual dependencies. I am using requirejs, and the code for my module looks something like this:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
How can I mock out hurp
and durp
so I can effectively unit test?
Upvotes: 130
Views: 33832
Reputation: 12846
This answer is based on Andreas Köberle's answer.
It wasn't that easy for me to implement and understand his solution, so I'll explain it in a bit more detail how it works, and some pitfalls to avoid, hoping that it will help future visitors.
So, first of all the setup:
I'm using Karma as test runner and MochaJs as test framework.
Using something like Squire didn't work for me, for some reason, when I used it, the test framework threw errors:
TypeError: Cannot read property 'call' of undefined
RequireJs has the possibility to map module ids to other module ids. It also allows to create a require
function that uses a different config than the global require
.
These features is crucial for this solution to work.
Here is my version of the mock code, including (a lot) comments (I hope its understandable). I wrapped it inside a module, so that the tests can easily require it.
define([], function () {
var count = 0;
var requireJsMock= Object.create(null);
requireJsMock.createMockRequire = function (mocks) {
//mocks is an object with the module ids/paths as keys, and the module as value
count++;
var map = {};
//register the mocks with unique names, and create a mapping from the mocked module id to the mock module id
//this will cause RequireJs to load the mock module instead of the real one
for (property in mocks) {
if (mocks.hasOwnProperty(property)) {
var moduleId = property; //the object property is the module id
var module = mocks[property]; //the value is the mock
var stubId = 'stub' + moduleId + count; //create a unique name to register the module
map[moduleId] = stubId; //add to the mapping
//register the mock with the unique id, so that RequireJs can actually call it
define(stubId, function () {
return module;
});
}
}
var defaultContext = requirejs.s.contexts._.config;
var requireMockContext = { baseUrl: defaultContext.baseUrl }; //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here
requireMockContext.context = "context_" + count; //use a unique context name, so that the configs dont overlap
//use the mapping for all modules
requireMockContext.map = {
"*": map
};
return require.config(requireMockContext); //create a require function that uses the new config
};
return requireJsMock;
});
The biggest pitfall I encountered, which literally cost me hours, was creating the RequireJs config. I tried to (deep) copy it, and only override the necessary properties (like context or map). This does not work! Only copy the baseUrl
, this works fine.
To use it, require it in your test, create the mocks, and then pass it to createMockRequire
. For example:
var ModuleMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);
And here an example of a complete test file:
define(["chai", "requireJsMock"], function (chai, requireJsMock) {
var expect = chai.expect;
describe("Module", function () {
describe("Method", function () {
it("should work", function () {
return new Promise(function (resolve, reject) {
var handler = { handle: function () { } };
var called = 0;
var moduleBMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleBIdOrPath": moduleBMock
}
var requireMocks = requireJsMock.createMockRequire(mocks);
requireMocks(["js/ModuleA"], function (moduleA) {
try {
moduleA.method(); //moduleA should call method of moduleBMock
expect(called).to.equal(1);
resolve();
} catch (e) {
reject(e);
}
});
});
});
});
});
});
Upvotes: 2
Reputation: 57
if you want to make some plain js tests which isolate one unit, then you can simply use this snippet:
function define(args, func){
if(!args.length){
throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})");
}
var fileName = document.scripts[document.scripts.length-1].src;
// get rid of the url and path elements
fileName = fileName.split("/");
fileName = fileName[fileName.length-1];
// get rid of the file ending
fileName = fileName.split(".");
fileName = fileName[0];
window[fileName] = func;
return func;
}
window.define = define;
Upvotes: 0
Reputation: 20184
I have found three different solutions to this problem, none of them pleasant.
define('hurp', [], function () {
return {
beans: 'Beans'
};
});
define('durp', [], function () {
return {
beans: 'durp beans'
};
});
require('hurpdhurp', function () {
// test hurpdurp in here
});
Fugly. You have to clutter up your tests with lots of AMD boilerplate.
This involves using a separate config.js file to define paths for each of the dependencies that point to mocks instead of the original dependencies. This is also ugly, requiring the creation of tons of test files and configurations files.
This is my current solution, but is still a terrible one.
You create your own define
function to provide your own mocks to the module and put your tests in the callback. Then you eval
the module to run your tests, like so:
var fs = require('fs')
, hurp = {
beans: 'BEANS'
}
, durp = {
beans: 'durp beans'
}
, hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8');
;
function define(deps, cb) {
var TestableHurpDurp = cb(hurp, durp);
// now run tests below on TestableHurpDurp, which is using your
// passed-in mocks as dependencies.
}
// evaluate the AMD module, running your mocked define function and your tests.
eval(hurpDurp);
This is my preferred solution. It looks a little magic, but it has a few benefits.
eval
in anger, and imagine Crockford exploding with rage.It still has some drawbacks, obviously.
define
in every test, since that is where your tests actually run.I am working on a test runner to give a nicer syntax for this kind of stuff, but I still have no good solution for problem 1.
Mocking deps in requirejs sucks hard. I found a way that sortof works, but am still not very happy with it. Please let me know if you have any better ideas.
Upvotes: 17
Reputation: 110932
So after reading this post I came up with a solution that use the requirejs config function to create a new context for your test where you can simply mock your dependencies:
var cnt = 0;
function createContext(stubs) {
cnt++;
var map = {};
var i18n = stubs.i18n;
stubs.i18n = {
load: sinon.spy(function(name, req, onLoad) {
onLoad(i18n);
})
};
_.each(stubs, function(value, key) {
var stubName = 'stub' + key + cnt;
map[key] = stubName;
define(stubName, function() {
return value;
});
});
return require.config({
context: "context_" + cnt,
map: {
"*": map
},
baseUrl: 'js/cfe/app/'
});
}
So it creates a new context where the definitions for Hurp
and Durp
will be set by the objects you passed into the function. The Math.random for the name is maybe a bit dirty but it works. Cause if you'll have a bunch of test you need to create new context for every suite to prevent reusing your mocks, or to load mocks when you want the real requirejs module.
In your case it would look like this:
(function () {
var stubs = {
hurp: 'hurp',
durp: 'durp'
};
var context = createContext(stubs);
context(['yourModuleName'], function (yourModule) {
//your normal jasmine test starts here
describe("yourModuleName", function () {
it('should log', function(){
spyOn(console, 'log');
yourModule.foo();
expect(console.log).toHasBeenCalledWith('hurp');
})
});
});
})();
So I'm using this approach in production for a while and its really robust.
Upvotes: 66
Reputation: 1025
You can use testr.js to mock dependencies. You can set testr to load the mock dependencies instead of the original ones. Here is an example usage:
var fakeDep = function(){
this.getText = function(){
return 'Fake Dependancy';
};
};
var Module1 = testr('module1', {
'dependancies/dependancy1':fakeDep
});
Check out this as well: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
Upvotes: 9
Reputation: 2187
you might want to check out the new Squire.js lib
from the docs:
Squire.js is a dependency injector for Require.js users to make mocking dependencies easy!
Upvotes: 46
Reputation: 4385
There's a config.map
option http://requirejs.org/docs/api.html#config-map.
On how-to use it:
Configure RequireJS expicitely;
requirejs.config({
map: {
'source/js': {
'foo': 'normalModule'
},
'source/test': {
'foo': 'stubModule'
}
}
});
In this case for normal and test code you could use the foo
module which will be real module reference and stub accordingly.
Upvotes: 15