How to write testable requirejs modules

I'm new to unit testing, so maybe something is missing for me, but how can I structure requirejs modules to make them fully testable? Consider an elegant sample module template.

define([], function () { "use strict"; var func1 = function(){ var data = func2(); }; var func2 = function(){ return db.call(); }; return { func1 : func1 } }); 

As far as I know, this is the most common template for building requirejs modules. Please correct me if I am wrong! Therefore, in this simplified scenario, I can easily check the return values ​​and the func1 behavior, since it is global. However, to check out func2 , I would also have to return its link. Correctly?

 return { func1 : func1, _test_func2 : func2 } 

This makes the code a little less cute, but overall still fine. However, if I wanted to ridicule func2 and replace its return value with Jasmine spy , I would not be able to, because this method is inside the closure.

So my question is how to completely structure requirejs modules? Are there more efficient templates for this situation than expanding a module template?

+7
javascript unit-testing requirejs karma-runner jasmine
source share
2 answers

If the module functions call the module with other functions directly (i.e., using links that are local to the module), it is impossible to intercept these calls from the outside. However, if you change your module so that the functions inside it call the functions of the module as well as outside it, then you can intercept these calls.

Here is an example that will allow you:

 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 the bar , and not to call it directly.

I gave a runnable example here . The spec/main.spec.js contains:

  expect(moduleA.foo()).toEqual("original"); spyOn(moduleA, "bar").andReturn("patched"); expect(moduleA.foo()).toEqual("patched"); 

You will notice that bar is a fixed function, but foo affects the correction.

In addition, in order to avoid constant contamination of exports with test code, I sometimes did an environmental check to determine if the module was running in a test environment and exported the functions necessary for testing only in test mode. Here is an example of the actual code that I wrote:

 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 ) to have the test option set to true, then the export additionally contains the __test symbol, which contains some additional elements that I want to export when I test the module. Otherwise, these characters are not available.

Edit: if you are concerned about the first method described above that you need to prefix all calls to internal functions with exports , you can 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 in _dynamic and provides local characters that allow them to be referenced without going through exports . If debug is false, then the function is not exported or wrapped. I updated the example to show this method. This is moduleB in the example.

+5
source share

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

I think that developers miss the point of unit tests when they try to write tests for private functions.

Dependencies are what we get from balls when developing software. And the more dependencies, the harder it is compressed. Therefore, if you have many tests that depend on the internal operation of the module, it will be very painful if you want to change the internal implementation. Therefore, keep your tests dependent on the open interface and keep private personal data.

My advice:

  • Create a public interface for your module.
  • Write a test against the open interface to indicate some expected behavior.
  • Inject the code required to pass this test.
  • Reactor (if necessary)
  • Repeat from step 2 until all functions are identified by tests and all tests pass.

At the stages of implementation and refactoring, the internal modules of the module will change. For example, func2 can be divided into different functions. And the danger is that if you have tests for func2 specifically, then you may have to rewrite the tests when you reorganize.

One of the main advantages of unit tests is that they guarantee that we do not violate existing functions when changing the internal functioning of a module. You lose this benefit if refactoring means you need to update 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 behavior with unit tests against the open interface. The goal is for small, well-tested modules that have a clear public interface.

If you're looking for help with unit testing, I highly recommend Kent Beck's TDD Case Study. Poorly written unit tests will be a hindrance, not a benefit, and in my opinion TDD is the only way to go.

+7
source share

All Articles