Thursday, September 17, 2015

Mock your Dojo AMD modules with StubModule.js

When testing AMD modules it is sometimes necessary to verify how it interacts with it's dependencies. For example, you might be writing a module that makes XHR requests using dojo/request and you want to make sure that it's passing the correct parameters. How would you test this? Creating a wrapper around the request method in your module and then spying on that method would work. You could also store the request method as a property of your module and spy on that in your tests. However, both of these solutions lead to messy code and there's something that feels wrong to me when adding code to production modules just for testing purposes.

You might think that it would be as easy as adding a map config to the Dojo loader and pointing dojo/request to a mocked module. While this is a possible solution it means that you have to create a separate file for each mock that you use and it gets very messy if you want to mock the same module multiple times within a single test page (since modules are cached by the loader).

StubModule.js provides a cleaner way to solve this problem. It allows you to stub modules with no dependencies on external files and no side effects to pollute your other tests. It does this by using the map config mentioned above as well as require.undef which is a Dojo-specific method that removes a module from the cache.

Using this tool is fairly straight forward. stub-module.js returns a single method that accepts two parameters. The first is the module identifier (MID) of the module that you want to test. The second is an object with keys that are MID's of the dependencies that you want to mock and values that are the mocked returned values. The method returns a promise that resolves with the stubbed module. For example (using Jasmine):

it('this is a demo', function (done) {
    var stub = jasmine.createSpy('request');
    stubModule('test/Module', {'dojo/request': stub}).then(function (StubbedModule) {
        var testObject = new StubbedModule();
        testObject.makeRequest();
 
        expect(stub).toHaveBeenCalledWith('some/url');
 
        done();
    });
});

To be honest I was surprised that I couldn't find an existing project that met my use case before I wrote this project. Did I miss something? Also, I wonder if the API of this project could be simplified. Any suggestions?

3 comments:

  1. Wow, this is exactly what I've been hunting for for days now! The only problem is that I can't get it to work. So far I've only tried it in Intern though. I saw an older post of yours that said you've only used it in Jasmine. Is that still the case or have you gotten it running in anything else?

    ReplyDelete
    Replies
    1. I don't think that I've done it with Intern before but I don't see any reason why it wouldn't work. Open a new issue in GitHub with the specifics of your problem and maybe we can figure out what's going on.

      https://github.com/agrc/StubModule/issues

      Delete
    2. Thanks Scott! Here's the issue:

      https://github.com/agrc/StubModule/issues/7

      As you'll see, I think I figured out what's going on. Now I'm just not sure what the best solution is.

      Delete