Mocking RequireJS modules in unit tests

Published: 2014-01-01 by Lars  testing

On one of our projects we structure our front-end JavaScript code using RequireJS modules, ensuring predictable load ordering and better encapsulation. When writing unit tests for code that depends, directly or indirectly, on one or more other modules, we often want to mock some of those modules to make our unit tests more focused giving us faster and more precise feedback. For example, when testing a controller, we might want to use a mock model, instead of the real model.

Some RequireJS modules might return an object with a couple of static properties and functions. In that case we can typically get by mocking the individual properties and functions using something like SinonJS. But some RequireJS modules return just a function (e.g. a constructor function, or a function representing a compiled template). We have been using the technique described by Paweł Maciejewski in Mocking Require.js dependencies to do this. This technique relies on adding an additional mapping layer on top of the usual RequireJS configuration, mapping to the mock module instead of the real module. In itself, this works fine, until you also need to use the real module, e.g. when testing the real module. In essence, the technique described by Paweł shows how to write the code for setting up the mock, but not the code for tearing it down again. This blog post is meant as an appendix, showing how you can also tear down your mocks after use.

Here is a simplified example with a Model class and a Controller class, where the controller class depends on Model:

define(function () {
    var Model = function () {
        this.state = 'real';
    };
    return Model;
});
define(['model'], function (Model) {
    var Controller = function () {
        this.model = new Model();
    };
    return Controller;
});

Using the mapping technique, we can write a mock Model like this:

define('modelMock', function () {
    return function () {
        this.state = 'fake';
    };
});

If we can replace the real Model with this mocked Model, this test should pass:

require(['controller'], function (Controller) {          
    var controller = new Controller();                   
    equal(controller.model.state, 'fake', 'model.state');
    start();                                             
});                                                      

and if we can then tear down the mocked Model and go back to the real model, this test should pass:

require(['controller'], function (Controller) {          
    var controller = new Controller();                   
    equal(controller.model.state, 'real', 'model.state');
    start();                                             
});                                                      

We need to do a couple of things to make this work:

First we need to reset the mapping to what it was. It would be nice if RequireJS had a way of giving us the current configuration so we could easily reset the configuration to a known state, but as that appears not to be possible, we need to do something like this instead:

setup: function () {                                 
    require.config({                                 
        map: {                                       
            '*': {                                   
                'model': 'modelMock'                 
            }                                        
        }                                            
    });                                              
},                                                   
teardown: function () {                              
    require.config({                                 
        map: {                                       
            '*': {                                   
                'model': 'model'                     
            }                                        
        }                                            
    });                                              
}                                                    

Another issue is that RequireJS will cache modules for us. When we do require(['Controller'], function () { ... }) for the second time, we get the exact same Controller function back, as we did the first time, disregarding whatever configuration changes we might have made. However, we can force RequireJS to recreate the module by removing the definition. We can do that with:

require.undef('controller');

To clean up properly after ourselves, we might also want to undefine the mock model module:

require.undef('modelMock');

We can now mock modules without risking that those mocks will accidentally impact other areas of our tests. A working sample project can be found at https://github.com/larsthorup/jsdevenv-qunit-require.

Discuss on Twitter