ponyfoo.com

Testing JavaScript Modules with Tape

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

I held a survey last monday where I asked for feedback about the kinds of things you want me to write about. It was quite useful, and one of the topics that seem to interest quite a few people is regarding module testing. Using tape, proxyquire, and sinon is the best possible combination of modules when it comes to testing JavaScript applications, regardless of them being server-side or front-end code.

In this article well get an in-depth look at these three modules (tape, proxyquire, and sinon), learn why they are better than the competition, how to use them, what each of them is good for, and how they complement each other to provide the ultimate “testing harness”, figuratively speaking, no test harness is actually needed!

While everything else in the JavaScript universe seems to be moving at blisteringly fast speeds, and accelerating, testing is in this weird place where globals are mystically okay. Frameworks like mocha and jasmine dominate the field, while they require a test harness, litter the global object with variables, and generally go against established best practices. I’ve always found it kind of really weird that test frameworks, which are supposed to be used to improve the code quality around a codebase, may end up doing the exact same opposite by encouraging the usage of globals and awkward monkey patching.

Nevertheless, let’s jump into the awesome world of testing with tape, proxyquire, and sinon, right after a brief note about the survey I ran last monday.

A Quick Note About the Survey

Many thanks to everyone who took the time to answer the survey. You gave me a few ideas about the kind of content I could write, and I also got some very useful insight into what people reading this blog enjoy (and dislike) the most.

I suppose the results themselves aren’t that interesting to anyone that’s not myself, but here they are anyways, for the sake of transparency. In general, people would like to learn more best practices, guidelines, code quality, tutorials, and in-depth articles, and not so much about rants.

What type of content is liked the most
What type of content is liked the most

I’ll do my best to keep the rants in check, but keep in mind the tagline of this blog!

A Brief Rant About Mocha et al

Mocha coffee is espresso mixed with hot milk, but with added chocolate. Now, I know what you must be thinking, but this doesn’t have anything to do with coffee. Rather, it’s all about the mocha. Mocha ain’t that good for you either. Suppose you are considering to implement tests in that project of yours at work. *Finally!*, you can hear your superiors whispering. At long last will your project be validated by a battery of tests that can be executed on every build. We’ll be able to fail fast by stopping deployments on their tracks whenever a single test fails. Our production systems won’t crash as often (hopefully never at all), and code quality overall will improve a thousandfold!

There’s plenty of testing suites out there, large and small, but in this article we’ll have to focus on the small ones, the unit testing libraries of the JavaScript universe. You have a choice to make. You need to pick the systems that will define your application’s quality standard from here on out.

Faced with this choice, should you pick a framework like Mocha? Let’s see.

var should = require('should');
var personCtrl = require('../controllers/person');

describe('personCtrl', function () {
  it('is literally littered with globals everywhere', function () {
    should(personCtrl.mouth).not.throw(Up);
  });
});

Mocha has plenty of globals like describe, it, beforeEach, before, after. Evidently, they couldn’t make up their mind with regards to assertion flavors, because testers love BDD while developers often prefer precise assertions. It’s up to you to pick the assertion engine, you do get the globals though! There’s no question about that! One unspoken problem about globals is that they make it that much harder to infer the API for a library just from usage alone. If the API is contained behind a module-wall like require('assert'), you can at least investigate: keys(assert) gives you the API methods (use Object.keys outside of the browser console), and you can do some blind poking. When the API is a fiery global-spilling ball of death, it’s much harder to tell. Sure, you can poke around the global object instead, but why go through the trouble?

If describe, before and it are globals, who knows what else is a global? Or why the hell assert is not one of them?

Setting aside the fact that should extends the Object prototype, there’s the whole “test harness” thing. You need to take some densely specific steps in order to be able to actually execute your tests. Want tests to run in the server and the browser? You’re going to have a hell of a time!

Mocha test runner example
Mocha test runner example

Running tests via the Mocha test harness in a single, succint command.

That’s enough caffè mocha for the day. Let’s shift our attention to a pleasant test engine that isn’t actually detrimental to the overall code quality in your applications.

Introducing Tape

It’s much better to go for a modular testing library, and tape fits the bill, so that’s what I’ve been using for the better part of the last few years.

var test = require('tape');
var personCtrl = require('../controllers/person');

test('we are the new anti-globalization protest front', function (t) {
  t.doesNotThrow(function () {
    personCtrl.mouth();
  });
  t.end();
});

Needless to say, you don’t have to use tape's built in assertions. You could use should (more like shouldn’t), but at least you get a default assertion engine. The describe aspect of the de-facto thousand-line long test file is translated into making smaller test files that are named just like the module you intend to unit test. it becomes test, and the only awkward piece of the puzzle is naming require('tape') test instead of tape.

Of course, you could always just name it tape if that weirds you out.

How is Tape better?

Besides not endorsing global warming, there’s a few other differences in how Tape operates. One of them, is how you define your tests. You may have noted that I used t.end in my example. That method is used to signal that a test method has ended. If your test is asynchronous, simply call t.end in your callback.

var test = require('tape');
var personCtrl = require('../controllers/person');

test('munching chews 50 times before swallowing a piece of food', function (t) {
  personCtrl.munch(function munched (err, result) {
    t.equal(err, null);
    t.equal(result.timing.pieces, 1);
    t.equal(result.timing.chewing, 50);
    t.end();
  });
});

Another approach to setting t.end might be using t.plan(count). Plan lets you specify how many assertions you expect your test to execute. This comes in handy sometimes, and can double as an extra assertion where you verify that all the other assertions are executed. Note that the test will fail if the count is off even by one, meaning that most of the time going for simply t.end will save you the hassle of updating t.plan statements with the correct assertion count. That being said, t.plan is a useful “guard clause” when you’re unsure whether a callback (presumably containing much needed assertions) will run or not.

Tape allows you to quickly turn off tests by changing test statements into test.skip, which is something that you can do with most test engines.

I’ve always been baffled about before, afterEach, and friends. I presume these were copied from mature test frameworks in other languages, but they’re largely useless in the dynamic prairie that is JavaScript.

You could just replicate those things with a few lines of code, for example:

function wrapper (description, fn) {
  test(description, function (t) {
    setup();
    fn(t);
    teardown();
  });
}

Then you could just use wrapper instead of test for those tests that you want to prepare and then dispose of. This is more transparent than just invoking the beforeEach global method inside the before and it globals and hoping for the best.

Given that this is just code and there aren’t mystifying globals laying around, you could easily come up with composable modules that are able to, for example, create a MongoDB database and drop it before and after every single test, providing the necessary isolation to individual integration tests.

Look ma, no harness!

Once you’re done writing your tests, you can simply execute them using node, no harness or test runners involved, imagine that!

node test/*.js

If you need to run the tests on a browser you could use testling after browserifying the test files.

browserify test/*.js | testling

Testling also offers the ability to run the tests in many different browsers by configuring the testling field in your package.json file.

Tap Out Reporters

Tape produces TAP output. TAP is a well-defined format that kind of looks like this:

1..9
ok 1 concurrent() should return the results as expected
ok 2 map() should return the results as expected
ok 3 waterfall() should run tasks in a waterfall
ok 4 series() should run tasks in a series as array
ok 5 series() should run tasks in a series as object
ok 6 series() should short-circuit on error
ok 7 concurrent() should run tasks concurrently as array
ok 8 concurrent() should run tasks concurrently as object
ok 9 concurrent() should short-circuit on error
# tests 9
# pass 9
# fail 0
1..1
ok 1 Test was run
#TAP meta information
0 errors

Just like Mocha, Tape provides you with a bunch of reporters that display fancier output than machine-readable TAP. One of the benefits of the protocol is that you could use these reporters with anything that produces TAP output, not just tape.

Leveraging proxyquire to mock modules

Besides using test databases, you could also mock services and libraries you don’t want to have an influence on the outcome of a particular test. There are plenty of ways of doing this, but I find that the best of them is to grab the dependency by its roots and remove it altogether. The proxyquire module makes the process quite easy for both the server and the browser.

There are some samples on how to use proxyquire in my book. Suppose you have a module ./timesTen like the one below:

var multiply = require('./multiply');

function timesTen (input) {
  return multiply(input, 10);
}

module.exports = timesTen;

Suppose also that you want to create some tests for ./timesTen, but you’ve already taken care of tests for ./multiply in another test file. Thus, you can mock ./multiply using proxyquire in your tests.

var proxyquire = require('proxyquire');
var test = require('tape');

test('timesTen returns output from multiply', function (t) {
  // arrange
  var multiplyStub = function (input) {
    return Infinity;
  };
  var timesTen = proxyquire('../timesTen', {
    './multiply': multiplyStub
  });
  
  // act
  var result = timesTen(20);

  // assert
  t.equal(result, Infinity);
  t.end();
});

Of course, that’s a horrendously simplistic way of testing something out, but it was also a very contrived example. In the real world you probably want to mock up a service that ends up accessing a database, Amazon S3, the Imgur API, or some other external service you really don’t want affecting the outcome of your tests. If we’re talking about the client-side, even better. You can use proxyquire to hide away interaction with the DOM, XHR, WebSockets, or anything that’s not pertinent to the test case.

Complementing proxyquire with Sinon.JS

Sinon.JS provides you with many ways in which you can mock objects in JavaScript. While proxyquire is the best thing you could be using to replace an entire module with a stub, sinon makes it quite easy to create the stubs that you’ll be providing to proxyquire.

For example, suppose a method you want to mock within a module is supposed to end up calling done(null, user). Using sinon, that becomes:

var sinon = require('sinon');
var user = { username: 'ponyfoo', id: 1234 };
var getUser = sinon.stub().yields(null, user);
var userService = {
  getUser: getUser
};

Now you can assert that the user is being properly utilized by comparing whatever you get back to the user mock you’ve defined that getUser should yield. Assuming you’re consuming the user service from within a controller, the snippet below could very well be a unit test for the controller, where you want to ensure that the response doesn’t contain the user’s id field.

var proxyquire = require('proxyquire');
var userController = proxyquire('../controllers/user', {
  '../services/user': userService
});
userController.prepareModel(function (err, model) {
  t.equal(err, null);
  t.equal(model.username, 'ponyfoo');
  t.notOk('id' in model);
  t.end();
});

A user controller that passes the test might look something like the following piece of code.

var userService = require('../services/user');

function prepareModel (done) {
  userService.getUser(1234, function (err, user) {
    if (err) {
      done(err); return;
    }
    done(null, { username: user.username });
  });
}

module.exports = {
  prepareModel: prepareModel
};

Just like it provides an excellent stubbing facility, sinon also allows you to spy on callbacks.

Using sinon to spy on callbacks

You can create a method that’s able to tell you how many times it was called, what arguments were used each time, and so on. Suppose that we just want to make sure the user controller calls userService.getUser at all. We can write a test like so:

var test = require('tape');
var sinon = require('sinon');
var proxyquire = require('proxyquire');

test('userController calls userService.getUser', function (t) {
  var getUser = sinon.spy();
  var userController = proxyquire('../controllers/user', {
    '../services/user': { getUser: getUser }
  });

  userController.prepareModel(function () {});

  t.equal(getUser.callCount, 1);
  t.end();
});

Just making sure the method was called once might not be enough, but you can also verify the precise arguments that were used.

t.equal(getUser.firstCall.args[0], 1234);
t.equal(typeof getUser.firstCall.args[1], 'function');
t.end();

At that point you might go the extra mile and invoke the callback yourself. Effectively setting up a “step-through” right in your test case, as you can control the exact timing with which your callbacks get invoked.

var user = { username: 'ponyfoo', id: 1234 };
getUser.firstCall.args[1](null, user);

All together now:

var test = require('tape');
var sinon = require('sinon');
var proxyquire = require('proxyquire');

test('userController calls userService.getUser', function (t) {
  var user = { username: 'ponyfoo', id: 1234 };
  var getUser = sinon.spy();
  var userController = proxyquire('../controllers/user', {
    '../services/user': { getUser: getUser }
  });

  userController.prepareModel(wrapUp);

  t.equal(getUser.firstCall.args[0], 1234);
  t.equal(typeof getUser.firstCall.args[1], 'function');

  getUser.firstCall.args[1](null, user);

  function wrapUp (err, model) {
    t.equal(err, null);
    t.equal(model.username, 'ponyfoo');
    t.notOk('id' in model);
    t.end();
  }
});

Using this technique you could make assertions about the callback that gets passed in userController to the userService.getUser method. Of course, this is not always necessary, as you typically want to be testing inputs against outputs, and never implementation details.

That way, if the underlying implementation changes, you won’t be invalidating tens of ineffective tests. As long as the inputs match the expected outputs, all should be well.

Conclusions

I realize that frameworks like Jasmine were probably built with testers in mind. You know, the kind of people who favor BDD and the Cucumber plain text human-readable DSL. More modular alternatives should be favored by developers.

The benefits of modular test frameworks like tape far outweight those of global-sprinklers such as Jasmine or Mocha, when it comes to developers writing maintainable tests. Regardless of your choice, sinon is an obvious inclusion in your test suites, as is proxyquire if you’re dealing with CommonJS modules.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (21)

davidchase wrote

Nice write up, tape is definitely a favorite of mine.

Daniele Polencic wrote

The only drawback when using Tape is debuggability, afaik. If you debug your application using debugger and node debug test.js, you’re fine. But if you use console.log to trace the flow, Tape automatically suppress those calls and it gets very hard to test your tests.

Your arguments on mocha globals seems pretty weak to me. I agree on globals being bad, but don’t mind too much if having those makes my testing easier.

Thanks for the write up. Nice, fresh content from a different perspective.

Daniele Polencic wrote

The only drawback when using Tape is debuggability, afaik. If you debug your application using debugger and node debug test.js, you’re fine. But if you use console.log to trace the flow, Tape automatically suppress those calls and it gets very hard to test your tests.

Your arguments on mocha globals seems pretty weak to me. I agree on globals being bad, but don’t mind too much if having those makes my testing easier.

Thanks for the write up. Nice, fresh content from a different perspective.

Nicolas Bevacqua wrote

It’s a matter of design. I wouldn’t choose a poorly designed code quality tool to improve code quality. Sure, it works. But if its end goal is to uphold code quality, then it shouldn’t spew globals all around. We’ve been avoiding that one for years!

tqwhite wrote

This is a complete deal killer for me.

I really buy the globals argument and have a huge affection for the simplicity of Tape. A little way into this new project, I actually rewrote the initial tests for Tape.

But then I started working on the project for real. I wrote a test, implemented the stub of the new method. Good. Then I wrote real functionality. When it didn’t work, I started to debug.

WTF!??

I have used almost everything for debugging but, at the end, I rely on print-trace, ie, console.log(), etc. I cannot for the entire life of me imagine why Tape doesn’t have the setting to prevent its subversion of those tools. Having a setting that does suppress them is good, too, but I am annoyed to have to retool with Mocha.

José Menor wrote

Daniele, to get info about tap and console.logs on your shell use

export TAP_DIAG=1

And don’t pipe the tests through faucet

More info here: https://github.com/isaacs/node-tap/issues/3

Christian Schuhmann wrote

If describe, before and it are globals, who knows what else is a global? Or why the hell assert is not one of them?

Just go read the code and you will see which variables are global. assert is not one of them because Mocha lets you choose which assertion library you want to use. I would not say there is a clear “winner”, since all alternative have their pros and cons.

Eric Elliott wrote

You could go dig into the Mocha source code to figure out whether or not its globals (and the assertion libs globals or object extension) are going to cause problems for you.

Or, you could stop wasting your time with sloppy tools and just use Tape.

This seems like a no-brainer to me.

Ian Fisher wrote

First, great writeup on tape, proxyquire, and sinon. I haven’t had a chance to use those yet so this was quite helpful!

However, I have to disagree with some of your complaints against Jasmine and Mocha. I know the Jasmine team and it was absolutely written for developers (and written by some of the best I have ever worked with).

It sounds to me like either you haven’t seen well written BDD style tests before or maybe they are just not your thing. To each his own, but I find the structure of BDD tests makes them easier to follow since a lot of the syntax is there to convey intent, which I find extremely useful for tests to have. The common BDD syntax also makes it easier to move between teams, projects, and even languages.

Regarding the global variables, sure globals are almost always bad, but I think this argument is ignoring why they are bad and simply stating the no-globals rule somewhat dogmatically. I avoid globals in my “real” code like the plague because I know the problems they introduce, but in 6+ years of writing tests with frameworks like Jasmine I have yet to be bitten once by its global variables (and I appreciate how much cleaner they make my tests).

All that said, I definitely like the simplicity of tape and being able to run it without a harness. That alone could make it the better choice for many situations!

lorenzo wrote

First things first, great write up on tape.js.

Now, here is my thinking:

When you compare tape over jasmine (for instance, on the basis of global variable pollution) you are seeing the testing problem from the wrong angle.

In the moment in which you are requiring a testing library you are depending on it, so I think we should ask ourselves the question do we need them at all.

Can I not instead just write flat, sequential simple code to test whatever I want to test ?

Something like this, for example (against a fictitious isEmail function)

If (isEmail(“test”) ) throw '';
If (isEmail(‘test@gmail.com’)==false) throw '';

Now if the program runs great, no error. If the program fails, telling me the line, I know exactly for which test/check.

I think the idea of moving the testing framework away from the code is a quite powerful one. I am doing some experiments on this idea, if anybody is interested you could see them here: http://www.yolpo.com/explore

Nicolas Bevacqua wrote

What you are describing would indeed work as a test file in any decent build system, because the program will exit with a non-zero code and the build would fail.

That being said, using a module like tape is a good compromise because it adds a standarized output format that you can conveniently parse into something else (like a diff or only printing failed assertions), and a few helper methods to make those assertions.

Your link results in a 404.

lorenzo wrote

I don’t know what happened to the formatting either. the correct link should be http://www.yolpo.com/explore.html

Renzo Canepa wrote

Hi Nicolas!.

First, I want to thank you for your post (it was very illuminating for me in multiple aspects).

Now, I am working with tape and sinon to construct my first unit test for a simple controller. However, my implementations make use of promises to handle the result of a data service. How can I make sinon work with promises instead of callbacks?

My situation can be represented with this:

function prepareModel (done) {
  userService.getUser(1234)
    .then(function(user) {
      done(null, { username: user.username });
    })
    .catch(function(err) {
      done(err); return;
    });
}

Finally, I wanted to know if your book covers this matter deeply enough to teach someone how is new to testing node applications and learn the basis behind it.

Lucas Holmquist wrote

I’d imagine you could use something like this:

https://www.npmjs.com/package/sinon-as-promised

Peter Lyons wrote
node test/*.js

This does not do what you indicate. Node will run the first matching path from the glob and pass the rest of them as command line arguments to it, which will just be ignored. Thus most of your tests won’t actually run. You probably meant:

tape test/*.js

That will actually run them all because tape will process all the command line arguments as tests to execute.

However, you could do:

find test -name '*.js' -print0  | xargs -0 -n 1 node

If you really wanted to run them directly with node instead of tape. You just need to be careful as node won’t call process.exit() for you and your tests are likely to hang if they make use of the network and don’t explicitly close all connections.

Federico Brigante wrote

Unfortunately testling is abandoned and there’s a big banner on the homepage saying that it doesn’t work “at the moment”

Matt wrote

Same issue with testling. Was able to get something similar with tape-run: https://github.com/juliangruber/tape-run

browserify tests/my-test.js | tape-run --browser chrome

Joe Lapp wrote

The creator of NPM maintains a tape-compatible module published as tap, but it’s far more flexible. https://github.com/tapjs/node-tap

I attempted to extend tape (with an e) to make it more suitable for debugging but found the code too fragile, requiring changes in too many places to do little things. I then looked at tap (without the e) and found I could do everything I wanted by wrapping it without trying to fix it.

The result is a test runner designed to help with debugging called subtap: https://github.com/jtlapp/subtap

Anyway, I arrived here for other reasons, looking to understand how to use tap/tape in end-to-end debugging.