Skip to content

Instantly share code, notes, and snippets.

@mikeapr4
Created February 12, 2016 16:28
Show Gist options
  • Save mikeapr4/e71af3c0b0a4113d1ab7 to your computer and use it in GitHub Desktop.
Save mikeapr4/e71af3c0b0a4113d1ab7 to your computer and use it in GitHub Desktop.
A mocking / injection mechanism for RequireJS
/******************************************************************************
* Inquire: a mocking mechanism for RequireJS (v2.1.9 - tightly coupled) *
* *
* Unversioned *
* *
* NOTE: Don't use Inquire for anything which modifies global scope! *
******************************************************************************/
var inquire;
(function(require) {
"use strict";
// Find the default context (it should be loaded with require.js script)
var defCtx = require.s.contexts._;
if (!defCtx) {
throw new Error("Inquire couldn't find default Requirejs context!");
}
// Note, this module can be "reloaded" without
// losing it's previous cache, this is necessary for
// the way coverage works.
if (!inquire) {
inquire = {
// cache of all modules' id/deps/factory function
modCache: {}
};
}
var modProto = defCtx.Module.prototype
, origInit = modProto.init;
/**
* Wrapper around the Module init function,
* best way to cache the dependency definition.
* @param depMaps
* @param factory
*/
modProto.init = function(depMaps, factory) {
// Factory is the function we want to keep access to
if (factory && this.map && this.map.id) {
inquire.modCache[this.map.id] = {
depMaps: depMaps,
factory: factory
};
}
origInit.apply(this, arguments);
};
/**
* Public function, should be used to get
* a new version of a *loaded* module, re-run
* with your mocks passed in instead of real
* classes.
* @param id String identifier of the module
* @param mocks String to object map of mocks
* @returns {*} Mock injected AMD module
*/
inquire.mock = function(id, mocks) {
var cache = inquire.modCache[id];
if (!cache) {
throw new Error("Inquire: Missing RequireJS definition, all mocks/injected classes need to be loaded first via RequireJS");
}
var deps = _.map(cache.depMaps, function(dep) {
return mocks[dep] || require(dep);
});
return cache.factory.apply(null, deps);
};
})(require);
@mikeapr4
Copy link
Author

Usage

location/my-class-to-test.js

define([
    'location/another-object',
    'location/some-other-object'
], function(AnotherObject, SomeOtherObject) {   

    ....

});

location/my-class-to-test-spec.js

define([
    'location/my-class-to-test'
], function(ClassToTest) {

    'use strict';

    var ClassToTest = inquire.mock('my-class-to-test',
    {
      'location/another-object': {someValue: 20}
    });

    ....

    // Tests etc here - new ClassToTest()

});

Notes

  • Alternative to https://github.com/iammerrick/Squire.js/ which needs a completely new context, here the class under test is created using dependencies from the existing context, mocks can be injected as exceptions.
  • The benefit here is that mocks can have the scope of a single test, meaning they can be mocked a different way for each class that depends on them.
  • There's no interference with the existing RequireJS context, it remains unchanged, though notice the warning about anything which changes global scope, the class passed into mock() will have it's declaration re-run meaning if there are changes to the global change, they will run again, which as you can imagine, might break things.
  • Downside is the tight coupling that was necessary due to the intentional work the RequireJS team has undertaken to keep inner workings of the library out of public access, however a mitigating factor is the short length of this code, it's easy to maintain. It's already gone through a minor-minor RequireJS upgrade without needing to be altered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment