Node.js Handbook - Testing Essentials
Recently I’ve noticed a lack of resources on advanced Node.js topics. There are plenty of guides and tutorials for getting started, but very little is written on maintainable design or scalable architecture. This post is a part of The Node.js Handbook, a series created to address this gap by sharing best practices. You can read more here.
This post will explain the tools needed to overcome the challenges of testing with Node.js. Together, they form an essential testing suite that will cover almost any project. The setup isn’t the most complex or feature-rich, but you could almost say that’s on purpose. If that sounds counter-intuitive… read on.
Introduction: Zero Points for Clever Tests
Before introducing the tools, it’s important to emphasis the reason we write tests in the first place: confidence. We write tests to inspire confidence that everything is working as expected. If something breaks we want to be sure we’ll catch it, and quickly understand what went wrong. Every line of every single test file should be written for this purpose.
The problem is that modern frameworks have gotten incredibly clever. This is ultimately a good thing, but it means we’ll need to be careful: this extra power is easily gained at the expense of clarity. Our tests may run faster or have more reusable code, but does that make us 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 our framework obfuscates this in the name of efficiency or cleverness, then it is doing us a disservice.
The Essential Toolkit
With that out of the way, lets introduce the four types of tools needed for successful Node.js testing:
- A Testing Framework (Mocha, Vows, Intern)
- An Assertion Library (Chai, Assert)
- Stubs (Sinon)
- Module Control (Mockery, Rewire)
A Testing Framework
The first and most important thing we’ll need is a testing framework. A framework will be our bedrock, providing a clear and scalable structure for our tests. We have a ton of options here, each with a different feature set and design. No matter which framework you go for, make sure you chose one that supports our mission: 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.
1 2 3 4 5 6 7 8 9 10
An Assertion Library
With a new testing framework in place, we’re ready to write some tests. The easiest way to do that is with an assertion library.
There are a ton of different libraries and syntax styles available for us 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 experimenting 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.
Unfortunately, assertions alone will only get us so far. When testing more complex functions, we’ll need a way to influence behavior and test code under explicit conditions. While it’s important to always stay true to the original behavior, 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.
1 2 3 4 5 6 7
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 (a smaller subset of stub features for measuring function calls) and Mocks (for setting expectations on behavior) to experiment with. An entire book could be written on all of Sinon’s features, but you can always start simple and experiment as you go.
We’re almost ready to start writing tests, but there’s still one last problem in our way:
require(). Because most calls to
require happen privately, we have no way to stub, assert, or otherwise access external modules. To really control our tests, we’ll need the option to override
require to return modules under our control.
There are a few different ways to accomplish this, depending on how much power is needed. Mockery gives us control of the module cache, and lets us replace entries with modules of our own. It’s a cautious library, and will warn developers when it thinks they’ve done something unintentional, like overwriting or forgetting to replace certain mocks. Just be sure to disable & deregister mocks after the tests have run.
1 2 3 4 5 6 7 8 9 10 11
Rewire is another popular tool that is much more powerful than Mockery. We can get and set private variables within the file, inject new code, replace old functions, and otherwise modify the original file however we’d like. This may sound like a better deal, but with all this power comes the cliched responsibility. Just because we can check/set a private variable doesn’t mean we should. These additional abilities move our tested code farther away from the original, 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.