Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write testable requirejs modules

I am new to unit testing so I might be missing something, but how am I supposed to structure requirejs modules in order to make them fully testable? Consider the elegant revealing module pattern.

define([], function () {
    "use strict";

    var func1 = function(){
        var data = func2();
    };
    var func2 = function(){
        return db.call();
    };

    return {
        func1 : func1
    }
});

As far as I am aware of this is the most common pattern for building requirejs modules. Please correct me if I am wrong! So in this simplistic scenario I can easily test return values and behavior of func1 since it is global. However, in order to test func2 I also would have to return it's reference. Right?

return {
    func1 : func1,
    _test_func2 : func2
}

This makes the code slightly less pretty, but overall is still ok. However, if I wanted to mock func2 and replace its return value by using Jasmine spy I would not be able to since that method is inside a closure.

So my question is how to structure requirejs modules to be fully testable? Are there better patterns for this situation than revealing module pattern?

like image 350
GEMI Avatar asked Oct 25 '13 11:10

GEMI


2 Answers

Are you sure you want to test the private function func2?

I think developers are missing the point of unit tests when they try writing tests for private functions.

Dependencies are what get us by the balls when developing software. And the more dependencies the tighter the squeeze . So if you have lots of tests dependant on the internal workings of a module it's going to be really painful when you want to change the internal implementation. So keep your tests dependant on the public interface, and keep the private stuff private.

My advice:

  1. Design the public interface to your module.
  2. Write a test against the public interface to specify some expected behaviour.
  3. Implement the code needed to pass that test.
  4. Refactor (if necessary)
  5. Repeat from step 2, until all the functionality has been defined by tests, and all the tests pass.

During the implementation and refactoring stages the internals of the module will change. For example, func2 could be split up into different functions. And the danger is that if you have tests for func2 specifically, then you may have to rewrite tests when you refactor.

One of the main benefits of unit tests is that they ensure we do not break existing functionality when we change a module's internal workings. You start losing that benefit if refactoring means you need to update the tests.

If the code in func2 becomes so complex that you want to test it explicitly, then extract it into a separate module, where you define the behaviour with unit tests against the public interface. Aim for small, well tested modules that have an easy to understand public interface.

If you are looking for help with regards to unit testing I thoroughly recommend Kent Beck's book "TDD by example". Having poorly written unit tests will become a hindrance rather than a benefit, and in my opinion TDD is the only way to go.

like image 145
GarethOwen Avatar answered Nov 13 '22 21:11

GarethOwen


If functions in a module call the module's other functions directly (i.e. by using references that are local to the module), there is no way to intercept these calls externally. However, if you change your module so that functions inside it call the module's functions in the same way code outside it does, then you can intercept these calls.

Here's an example that would allow what you want:

define([], function () {
    "use strict";

    var foo = function(){
        return exports.bar();
    };

    var bar = function(){
        return "original";
    };

    var exports =  {
        foo: foo,
        bar: bar
    };

    return exports;
});

The key is that foo goes through exports to access bar rather than call it directly.

I've put up a runnable example here. The spec/main.spec.js file contains:

    expect(moduleA.foo()).toEqual("original");

    spyOn(moduleA, "bar").andReturn("patched");

    expect(moduleA.foo()).toEqual("patched");

You'll notice that bar is the function patched but foo is affected by the patching.

Also, to avoid having the exports polluted by test code on a permanent basis, I've sometimes done an environmental check to determine whether the module is run in a test environment and would export functions necessary for testing only in testing mode. Here's an example of actual code I've written:

var options = module.config();
var test = options && options.test;

[...]
// For testing only
if (test) {
    exports.__test = {
        $modal: $modal,
        reset: _reset,
        is_terminating: _is_terminating
    };
}

If the requirejs configuration configures my module (using config) so that it has a test option set to a true value then the exports will additionally contain a __test symbol that contains a few additional items I want to export when I'm testing the module. Otherwise, these symbols are not available.

Edit: if what bothers you about the first method above is having to prefix all calls to internal functions with exports, you could do something like this:

define(["module"], function (module) {
    "use strict";

    var debug = module.config().debug;
    var exports = {};

    /**
     * @function
     * @param {String} name Name of the function to export
     * @param {Function} f Function to export.
     * @returns {Function} A wrapper for <code>f</code>, or <code>f</code>.
     */
    var _dynamic = (debug ?
        function (name, f) {
            exports[name] = f;
            return function () {
                // This call allows for future changes to arguments passed..
                return exports[name].apply(this, arguments);
            };
        } :
        _dynamic = function (name, f) { return f; });

    var foo = function () {
        return bar(1, 2, 3);
    };

    var bar = _dynamic("bar", function (a, b, c) {
        return "original: called with " + a + " " + b + " " + c;
    });

    exports.foo = foo;

    return exports;
});

When the RequireJS configuration configures the module above so that debug is true, it exports the functions wrapped by _dynamic and provides local symbols that allow referring to them without going through exports. If debug is false, then the function is not exported and is not wrapped. I've updated the example to show this method. It's moduleB in the example.

like image 6
Louis Avatar answered Nov 13 '22 22:11

Louis