130

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?

Jamison Dance
  • 19,896
  • 25
  • 97
  • 99
  • I am just doing some crazy eval stuff in node.js to mock out the `define` function. There are a few different options though. I'll post an answer in hopes that it will be helpful. – Jamison Dance Jul 27 '12 at 16:10
  • 1
    For unit testing with Jasmine you may also want to take a quick look at [Jasq](https://github.com/biril/jasq). [Disclaimer: I'm maintaining the lib] – biril Oct 08 '14 at 16:43
  • 1
    If you're testing in node env you could use [require-mock](https://github.com/ValeriiVasin/requirejs-mock) package. It allows you to easily mock your dependencies, replace modules etc. If you need browser env with async module load - you could try [Squire.js](https://github.com/iammerrick/Squire.js/) – ValeriiVasin Mar 23 '15 at 10:27

7 Answers7

66

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.

alex
  • 479,566
  • 201
  • 878
  • 984
Andreas Köberle
  • 106,652
  • 57
  • 273
  • 297
  • 1
    I like what you're doing here... especially since you can load a different context for each test. The only thing I wish I could change is that it seems like it's only working if I mock out all of the dependencies. Do you know of a way to return the mock objects if they are there, but fallback to retrieving from the actual .js file if a mock is not provided? I've been trying to dig through the require code to figure it out, but I'm getting a little lost. – Glen Hughes Jul 29 '12 at 00:36
  • 5
    It only mocks the dependency you pass to the `createContext` function. So in your case if you only pass `{hurp: 'hurp'}` to function, the `durp` file will be loaded as a normal dependency. – Andreas Köberle Jul 29 '12 at 08:48
  • You are right... not sure what I was doing wrong the first time around. Great job, this is going to come in handy... Thanks a lot!!! – Glen Hughes Jul 30 '12 at 14:11
  • I like this way of doing it, probably the best answer so far. +1 – Evan Larsen Aug 01 '12 at 19:14
  • 1
    I'm using this in Rails (with jasminerice/phantomjs) and it has been the best solution I've found for mocking with RequireJS. – Ben Anderson Oct 26 '12 at 19:09
  • 13
    +1 Not pretty, but of all possible solutions this seems to be the least ugly/messy one. This problem deserves more attention. – Chris Salzberg Oct 28 '12 at 03:20
  • 1
    Update: to anyone considering this solution, I'd suggest checking out squire.js (https://github.com/iammerrick/Squire.js/) mentioned below. It's a nice implementation of a solution which is similar to this one, creating new contexts wherever stubs are needed. – Chris Salzberg Jun 24 '13 at 14:08
  • I don't know yet why, but your solution doesn't work for me. Dependencies are stubbed correctly, but spec definitions (2nd parameter to describe function) are not executed. I'm using RequireJS-2.1.9, Jasmine-13.1.3 and running tests with maven-jasmine-plugin, Anyone has an idea? – miszczu Nov 08 '13 at 10:51
  • @AndreasKöberle: This is making the test cases to run twice. Any idea why ? – Ant's May 25 '15 at 05:25
  • Can you please extend with regards to how you run your tests? Where does the `window.__karma__.start()` callback is called? – Madara's Ghost Sep 15 '15 at 11:34
  • I found this post looking for an alternative to testrjs library which I was using in my test setup. I managed to replace it by using this code with some tweaks for my setup. Thanks allot @AndreasKöberle great solution! – Michael Oct 16 '16 at 06:50
46

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!

busticated
  • 2,147
  • 1
  • 20
  • 20
  • 2
    Strongly recommended! I'm updating my code to use squire.js and so far I'm liking it a lot. Very very simple code, no great magic under the hood, but done in a way that is (relatively) easy to understand. – Chris Salzberg Jun 24 '13 at 14:06
  • 2
    I've had a lot of problems with squire side effecting other tests and cannot recommend it. I would recommend https://www.npmjs.com/package/requirejs-mock – Jeff Whiting Sep 13 '16 at 14:36
17

I have found three different solutions to this problem, none of them pleasant.

Defining Dependencies Inline

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.

Loading Mock Dependencies From Different Paths

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.

Fake It In Node

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.

  1. Run your tests in node, so no messing with browser automation.
  2. Less need for messy AMD boilerplate in your tests.
  3. You get to use eval in anger, and imagine Crockford exploding with rage.

It still has some drawbacks, obviously.

  1. Since you are testing in node, you can't do anything with browser events or DOM manipulation. Only good for testing logic.
  2. Still a little clunky to set up. You need to mock out 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.

Conclusion

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.

Ian Nelson
  • 57,123
  • 20
  • 76
  • 103
Jamison Dance
  • 19,896
  • 25
  • 97
  • 99
15

There's a config.map option http://requirejs.org/docs/api.html#config-map.

On how-to use it:

  1. Define normal module;
  2. Define stub module;
  3. 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.

Artem Oboturov
  • 4,344
  • 2
  • 30
  • 48
  • This approach worked really well for me. In my case, I added this to the html of the test runner page -> map: { '*': { 'Common/Modules/usefulModule': '/Tests/Specs/Common/usefulModuleMock.js' } } – AlignedDev Aug 18 '14 at 16:20
9

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/

janith
  • 1,025
  • 5
  • 21
  • 2
    I really wanted testr.js to work, but it doesn't feel quite up to the task yet. In the end I'm going with @Andreas Köberle's solution, which will add nested contexts to my tests (not pretty) but which consistently works. I wish someone could focus on resolving this solution in a more elegant way. I'll keep watching testr.js and if/when it works, will make the switch. – Chris Salzberg Oct 28 '12 at 03:19
  • @shioyama hi, thanks for the feedback! I'd love to take a look at how you have configured testr.js within your test stack. Happy to help you fix any issues you might be having! There's also the github Issues page if you want to log something there. Thanks, – Matty F Dec 05 '12 at 03:20
  • 1
    @MattyF sorry I don't even recall right now what the exact reason was that testr.js didn't work for me, but I've come to the conclusion that the use of extra contexts is actually quite alright and in fact in line with how require.js was meant to be used for mocking/stubbing. – Chris Salzberg Jun 24 '13 at 14:10
2

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.

Usage

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);
                        }
                    });
                });
            });
        });
    });
});
Community
  • 1
  • 1
Domysee
  • 12,718
  • 10
  • 53
  • 84
0

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;
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339