Node.js Testing Doesn’t Need to be Difficult

Whenever testing code in the context of Node.js comes up in conversation, it’s very likely to be accompanied by an exploration of testing-related tooling and strategies. If you’ve ever participated in such a discussion, it’s not uncommon to hear one or more of the following terms thrown around: test runners, assertion libraries, mocking, stubbing, spying, and continuous integration. While these can certainly be valuable tools to consider as you build out a full test suite, the thought of familiarizing oneself with each can be intimidating to a developer new to the Node.js ecosystem or testing in general.

But you don’t need any of these tools to write a basic test. The idea behind a test is to make an assumption about your code that you’d expect to be true. In cases where these expectations are in fact untrue, the script should abort with a failing exit code. Typically the script is terminated by an uncaught error.

Let’s imagine we have a simple sum function which accepts two arguments where the return value is the sum of the two arguments.

function sum( x, y ) {
    return x + y;

Our most basic test might simply consider a summing of two arbitrary numbers for which we’d expect the assumed value to be returned.

var calculated = sum( 1, 2 );

if ( calculated !== 3 ) {
    throw new Error( ‘3 was expected, but we saw ‘ + calculated );

If we were to run this script, we’d see that it quietly finishes because the summed value is equal to our expected assumption. If we were to tamper with our original sum function by, say, changing the + operator to -, we’d see that it terminates with an uncaught error using the message we specified.

That’s really all there is to basic testing. We could optionally choose to add this as a script in our project’s package.json so that it could be predictably run through the npm test command.

As you continue to build more tests, you’ll want to consider the topics listed in the first paragraph, as they’ll help to extend and simplify common usage patterns. A brief description of each is included below:

  • Assertion libraries define a syntax for declaring your assumptions. Node includes a core module (assert) which provides a limited assertion syntax.
  • Test runners provide a means to combine your tests into a logical grouping, and often provide a reporting interface to more easily interpret the results of your tests.
  • Mocking, stubbing, and spying are related topics which allow you to create simulated functions which can then be observed, granting you insight into how your code is executed without the need to actually execute it.
  • Continuous integration is a service which will automatically run tasks (including tests) when the code is changed.