Skip to content

Demystifying Node.js test runner assertions

If you're wondering which assertions you should use to test your code then read this article to get the answer

Starting in Node.js v18.0.0, a new built-in test runner was introduced and later became stable in v20.0.0. This is a big step forward for the entire JavaScript ecosystem. It simplifies project setups and reduces the need for complex test configuration. Plus, it eliminates some of the ever-so-common challenges that we have all faced when using your favourite test runner. Features like ESModule support are included natively and it helps reduce those frustrating, hard-to-debug, false negative errors that can pop up and kill your productivity.

However, once you dive into writing tests with the built-in test runner, you might find yourself scratching your head, asking, “Which assertions should I use to test my code?” The good news is that the Node.js project has streamlined the assertion party that exists in other frameworks, instead focusing on a handful of key assertions. Let’s break it down.

Simplified testing with Node.js

The built-in Node.js test runner is a huge leap forward for the JavaScript community. For most cases, you can say goodbye to installing, configuring and troubleshooting those third party testing libraries. Not only will this simplify the mental load and save you time, but it can also eliminate certain tricky errors. For example, have you ever experienced expect(error).toBeInstanceOf(Error) fail in another test runner? To dive deeper into why you might reconsider using your favorite third-party library, check out this excellent blog post from Nearform's Manuel Spigolon, a Fastify.js maintainer.

After you’ve decided to take the leap into testing bliss with the built-in Node.js test runner, the first question you will probably ask is, “Where are all of the assertions that I’m used to?” Not to fear, the built-in test runner has you fully covered — pun intended. While the test runner offers fewer assertions than some other libraries, that’s by design. The team behind Node.js has embraced a “less is more” philosophy. This keeps things simple, transparent, and elegant. The complex assertions that you may be used to tend to hide important details and can lead to surprise errors. However, with the built in test runner, you have the simple building blocks to build custom assertions that fit exactly to your domain. That’s smart thinking!

Understanding assertions in Node.js

We use assertions to force errors to be thrown and fail tests when a test condition is not satisfied. A typical assertion is to compare an actual value to an expected value. If the actual value does not equal the expected value, the test condition is not satisfied and the test should fail.

Let’s dive into the assertions listed in the current version (as of writing) of the Node.js documentation. The first thing to notice is something called “strict” assertion mode. What’s that all about? It’s tied to ECMAScript’s (JavaScript’s) long history with === (strict equality) and == (loose equality). The loose == operator performs type coercion and can yield unexpected results (e.g. '' == false). In contrast, === ensures strict type equality and predictability.

Strict vs legacy assertions

Time for a cheat sheet:

Strict assertions (prefer these):

  • assert.strictEqual(actual, expected[, message])

  • assert.deepStrictEqual(actual, expected[, message])

Legacy assertions (avoid these):

  • assert.equal(actual, expected[, message])

  • assert.deepEqual(actual, expected[, message])

That’s simple enough, but you may be asking, “Why avoid the legacy assertions?” The answer is also simple enough, they use == under the hood that can lead to unexpected outcomes and tests that are testing what was intended. For example:

js
assert.equal('+000', false); // Doesn't throw an error 😱

It is possible to enable “strict assertion mode” to avoid the pitfalls of legacy assertions. In strict assertion mode, equal and deepEqual behave like their strict counterparts. Strict assertion mode can be enabled by simply importing from node:assert/strict:

js
import assert from 'node:assert/strict';

Something new: assert.partialDeepStrictEqual

Node.js v23.4.0 added a new assertion, contributed by Nearform's Giovanni Bucci in his marathon 173-comment PR:

js
assert.partialDeepStrictEqual(actual, expected[, message]);

This assertion behaves the same for primitive types (strings, booleans, numbers, etc). However, for objects this assertion allows the actual object to have extra properties beyond what the expected object has. This is handy for test cases when you only care about specific properties. That is great for ignoring properties that you know might change between test runs, like timestamps and unique IDs.

A few other useful assertions

There are a few other useful assertions in the latest version of Node.js that are worth mentioning:

  • Negative assertions: Test for inequality, again, it’s recommended to only use the strict versions assert.notStrictEqual and assert.notDeepStrictEqual.

  • Exception handling: Verify that function throws (or doesn’t throw) with assert.throws and assert.doesNotThrow.

  • Async assertions: Inevitably, you will also need to work with promises and async functions. There’s assertions for that too: assert.doesNotReject and assert.rejects. Just don’t forget to await the assertion since it returns a promise.

  • Strings and regular expressions: Use assert.match to test string matching with regular expressions.

Conclusion

The built-in test runner wins for simplicity and stability. That’s why we love Node.js. By embracing the KISS principle, it enables developers to write simpler, reliable and maintainable tests. Always prefer the strict assertions and avoid anything legacy. Of course, using these small building blocks, developers can craft custom assertions that fit to their domain!

Testing doesn’t have to be complicated: simpler is better.

Be sure to check out some of the other articles that we’ve written at Nearform about the Node.js test runner. We have articles about using the test runner to test a Fastify application and how to build a custom test reporter. We also have an open source workshop that can help you up-skill with the Node.js test runner.

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.

Contact