The Node.js Way - Testing Essentials

May 14, 2014 (Updated December 18, 2014)

Setting up good testing can be tricky no matter what language you’re in. JavaScript’s flexibility can make your tests powerful in terms of access, but if you’re not careful that same flexibility can leave you tearing your hair out the next day. For instance, how do you test API callbacks? How do you deal with require? Without a proper setup, whether TDD is dead or not will end up meaning very little.

This post will explain the tools needed for efficient testing with Node.js. Together, they form an essential testing suite that will cover almost any project. The setup was designed to cover just the essentials, valuing simplicity over cleverness and advanced features. If that sounds counter-intuitive… read on.

Introduction: Zero Points for Clever Tests

Before introducing the tools, it’s important to emphasis why you should be writing tests in the first place: confidence. You write tests to inspire confidence that everything is working as expected. If something breaks you want to be sure that you’ll catch it, and quickly understand what went wrong. Every line of every test file should be written for this purpose.

The problem is that modern frameworks have gotten incredibly clever. This is ultimately a good thing, advanced tooling and technology can only benefit the developer. But it also means you’ll need to be careful: this extra power is easily gained at the expense of clarity. Your tests may run faster or have more reusable code, but does that make you more or less confident in what is actually being tested? Always remember: There are no points for clever tests.

Test clarity should be valued above all else. If you framework obfuscates this in the name of efficiency or cleverness, then it is doing you a disservice.

The Essential Toolkit

With that out of the way, lets introduce the essential Node testing kit. As you may remember, The Node Way values smaller, swappable, single-purpose tools over large do-everything-under-the-sun testing frameworks. In that spirit, the essential toolkit is a collection of smaller tools that each do one thing exceptionally well. They are:

A Testing Framework

The first and most important thing you’ll need is a testing framework. A framework will provide a clear and scalable bedrock for all of our tests. There are a ton of available options in this area, each with a different feature set and design. No matter which framework you go for, just make sure you chose one that supports writing clear, maintainable tests.

For Node.js, Mocha is the gold standard. It has been around forever, and is well tested and maintained. Its customization options are extensive, which makes it incredibly flexible as well. While the framework is far from sexy, its setup/teardown pattern encourages explicit, understandable, and easy-to-follow tests.

describe('yourModuleName', function() {
  before(function(){
    // The before() callback gets run before all tests in the suite. Do one-time setup here.
  });
  beforeEach(function(){
    // The beforeEach() callback gets run before each test in the suite.
  });
  it('does x when y', function(){
    // Now... Test!
  });
  after(function() {
    // after() is run after all your tests have completed. Do teardown here.
  });
});

An Assertion Library

With a new testing framework in place, you’re ready to write some tests. The easiest way to do that is with an assertion library.

assert(object.isValid, 'tests that this property is true, and throws an error if it is false');

There are a ton of different libraries and syntax styles available for you to use. TDD, BDD, assert(), should()… the list goes on. BDD has been gaining popularity recently thanks to its natural-language structure, but it should all come down to what feels best to you. Chai is a great library for experimentation because it supports most of the popular assertion styles. But if you’re a dependency minimalist, Node.js comes bundled with a simple assertion library as well.

var life = 40 + 2;
expect(life).to.equal(42); // BDD Assertion Style
assert.equal(life, 42);    // TDD Assertion Style

Stubs

Unfortunately, assertions alone can only get you so far. When testing more complex functions, you’ll need a way to influence the environment and test code under explicit conditions. While it’s important for good tests to stay true to the original code, sometimes we need to be certain that some function will return true, or that an API call will yield with an expected value. Sinon allows us to do this easily.

var callback = sinon.stub();
callback.withArgs(42).returns(1);
callback.withArgs(1).throws("TypeError");

callback();   // No return value, no exception
callback(42); // Returns 1
callback(1);  // Throws TypeError

Sinon includes a collection of other useful tools for testing, such as fake timers and argument matchers. In addition to Stubs, there are also Spies for watching functions are called and with what arguments, and Mocks for setting expectations on some behavior. Start simple and feel free to experiment as you go.

Module Control

You’re almost ready to start writing tests, but there’s still one last problem in your way: require(). Because most calls to require happen privately, you have no way to stub, assert, or otherwise access external modules from your tests. To really control them, you’ll need to control require.

There are a few different ways to accomplish this, depending on how much power is needed. Mockery lets you populate the internal module cache and replace modules to be loaded with objects of our own. Just be sure to disable & deregister mocks after the tests have run.

before(function() {
  mockery.enable();
  // Allow some ...
  mockery.registerAllowable('async');
  // ... Control others
  mockery.registerMock('../some-other-module', stubbedModule);
});
after(function() {
  mockery.deregisterAll();
  mockery.disable();
});

Rewire is another popular mocking tool that is much more powerful than Mockery. With it, you can get and set private variables within the file, inject new code, replace old functions, and otherwise modify the original file. This may sound like a better deal, but with all this power comes the cliched responsibility. Just because you can check/set a private variable doesn’t mean you should. While tempting, rewriting and rewiring the private module scope like that brings your code farther away from the original module behavior, and can easily get in the way of writing good tests.

Bringing it all Together

To see these tools all working together check out a working example on GitHub. While I singled out a few favorite libraries above, there are plenty of good alternatives in each of the categories listed. Think I missed something important? Let me know in the comments, or on Twitter at @FredKSchott.