Testing within a javascript closure
Asked Answered
G

3

15

Is it possible to unit test javascript functions that exist within a closure, so for example, given the following:

(function() {
  var a = function() {
    //do something
  }
  window.b =  function() {
    // do something else
  }
})();

Is it possible to unit test function a without exposing it? If not, is there a good way to expose a, but only in test mode?

Goles answered 8/7, 2011 at 14:51 Comment(2)
Thanks for the replies. The general consensus is that to unit test within the closure, you must either a) expose the function as part of the API, or b) use a member of the closure to smuggle the function out.Goles
This is very similar to another SO question that I posted an idea at: https://mcmap.net/q/825552/-how-to-test-functions-inside-javascript-closure . Basically, I expose a function that does the "describe()" and "it()" stuff inside it.Brittney
G
1

Revisiting the question 4 years on, I now have a better solution.

Generate a testable version of your code as a build step.

Say I'm using Gulp to minify my JavaScript, and my code looks like this:

var a = function() {}
window.b = function() {}

Not the absence of a closure. Now I set up a gulp task and have it watch my code, like so:

gulp.task('js', function () {
  return gulp.src([dirs.js.src, '*.js'].join('/'))
    .pipe(wrap('(function() {\n<%= contents %>\n})();'))
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(gulp.dest(dirs.js.dest));
});

This will wrap my code in the IIFE producing the following output:

(function() {
var a = function() {}
window.b = function() {}
)();

Generating the testable version

Now I add another Gulp task, like this

gulp.task('testJs', function () {
  return gulp.src([dirs.test.src, '*.js'].join('/'))
    .pipe(wrap(
      [
        '(function() {',
          '<%= contents %>',
          '// Smuggle locals out of closure for testing',
          'window.a = a;',
        '})();'
      ].join('\n')
    ))
    .pipe(concat(package.name + '.testable.js'))
    .pipe(gulp.dest(dirs.test.dest));
});

This will generate the following unsafe but testable code right in my test directory:

(function() {
var a = function() {}
window.b = function() {}
// Smuggle locals out of closure for testing
window.a = a;
)();

Add a watch task...

gulp.task('watch', function() {
  gulp.watch([dirs.js.src, '**', '*.js'].join('/'), ['js', 'testJs']);
});

...and you're done.

See here

See here for an example:

https://github.com/forwardadvance/ng-tweets

Goles answered 2/7, 2015 at 14:13 Comment(0)
W
8

Your anonymous function could take a parameter which would be undefined when not in test mode, and say this parameter would be an object, you could fill the object with a's without exposing a directly.

Just my .02$

While answered 8/7, 2011 at 14:54 Comment(1)
Good answer, though it does involve modifying the original code to smuggle out the privates. A big plus is that the private function will still be in the closure, and have access to other members of the closure.Goles
C
3

The biggest question here is why do you want to keep it hidden? The fact that you have this function a that you want to test is an indicator that a has a responsibility of its own that should be tested. Pulling it out and getting it under test gains much more value when weighed against the minimal risk of exposing it. If you really want to keep it hidden, then I'd suggest something similar to Manux's response where you create an object that does what a does but doesn't expose a directly. Then you could test the behavior of a by testing that object.

Comeau answered 8/7, 2011 at 15:58 Comment(1)
Good answer. There are occasions though when it's handy. I wrote an application recently that needed to work out the number of days between two dates. It wasn't core funtionality and I would have liked to have TDDed it, and kept it hidden. As it was I exposed it and wrote the tests, but I would have preferred it to be private.Goles
G
1

Revisiting the question 4 years on, I now have a better solution.

Generate a testable version of your code as a build step.

Say I'm using Gulp to minify my JavaScript, and my code looks like this:

var a = function() {}
window.b = function() {}

Not the absence of a closure. Now I set up a gulp task and have it watch my code, like so:

gulp.task('js', function () {
  return gulp.src([dirs.js.src, '*.js'].join('/'))
    .pipe(wrap('(function() {\n<%= contents %>\n})();'))
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(gulp.dest(dirs.js.dest));
});

This will wrap my code in the IIFE producing the following output:

(function() {
var a = function() {}
window.b = function() {}
)();

Generating the testable version

Now I add another Gulp task, like this

gulp.task('testJs', function () {
  return gulp.src([dirs.test.src, '*.js'].join('/'))
    .pipe(wrap(
      [
        '(function() {',
          '<%= contents %>',
          '// Smuggle locals out of closure for testing',
          'window.a = a;',
        '})();'
      ].join('\n')
    ))
    .pipe(concat(package.name + '.testable.js'))
    .pipe(gulp.dest(dirs.test.dest));
});

This will generate the following unsafe but testable code right in my test directory:

(function() {
var a = function() {}
window.b = function() {}
// Smuggle locals out of closure for testing
window.a = a;
)();

Add a watch task...

gulp.task('watch', function() {
  gulp.watch([dirs.js.src, '**', '*.js'].join('/'), ['js', 'testJs']);
});

...and you're done.

See here

See here for an example:

https://github.com/forwardadvance/ng-tweets

Goles answered 2/7, 2015 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.