A blog about software development, written by Daniel Diekmeier. Archive.

Using the Built In Test Runner to make Node.js projects more sustainable

May 5, 2023

TL;DR: I converted four old Node.js projects to use the brand new built in test runner. This removed hundreds of dependencies and will make it easier to maintain these projects in the future.

I own a few npm packages that basically nobody uses. Despite this fact, Github sends me a lot of Dependabot alerts about critical security problems in some ancient version of minimist in a dependency of a dev dependency of a package I barely use.

I don't really care. This adds a lot of noise to my Github life and makes me more likely to miss important alerts. I already turned off Dependabot Alerts for a lot of old projects – especially if I don't use them myself.

Also, I'm currently reading Sustainable Web Development with Ruby on Rails by David Bryant Copeland. A big topic is the carrying cost of decisions you make while building your projects. (The book is very insightful and I recommend reading it.)

A carrying cost in this case is additional work you have to put in while trying to work on the project. For example, updating dependencies when they stop working or have security problems. Or fixing tests that break because of a change in a dependency. This is closely related to technical debt, but not quite the same. Technical debt is like paying of a loan you took out in the past. Carrying costs are more like the rent you pay for the house you live in.

One of my takeaways from the book is that whenever possible, you should try to make decisions that reduce the carrying cost of your project.

While not enormous, the carrying cost of my old Node.js projects is also not zero. I have to deal with Dependabot alerts, and I have to make sure that the tests still run.

Thankfully, the stars have finally aligned to make this a bit easier: Node.js v20 has just been released, and with it, Node.js now has a stable built in test runner: node:test.

I think this is great news. In my older packages, I often installed the Ava test runner. This pulled in about 300 gazillion transitive dependencies, which, down the line, would get marked as terrible security problems by Dependabot. With the new node:test module, I was able to remove this dependency completely.

The changes boiled down to this:

# Switching from ava to node:test
- import test from 'ava'
+ import { test } from 'node:test'
+ import assert from 'node:assert'

# Switching from t.is to assert.strictEqual
  test('converts to string', (t) => {
   const result = Spoons.convert(1)
-  t.is(result, '1 tsp')
+  assert.strictEqual(result, '1 tsp')

And running the tests is now as easy as node --test.

So, over the course of the weekend, I dusted off four old projects and migrated them to modern alternatives.

  • @danieldiekmeier/async-worker: I converted the project to ESM and removed all dependencies. Commit 8b24337, +40/-4,468 lines
  • todo-or-die: I converted the project to ESM and removed all dependencies. Commit 2e6b948, +39/-4,588 lines
  • Salt Bae: I switched from Parcel to SvelteKit and from Ava to the built in test runner. Commit d986fa0 +671/-5,956 lines, 3b4eb4b, +5,987/-8600 lines
  • Spotify Latest Releases: I switched from Vue, Koa and Webpack to SvelteKit. I moved from axios to native fetch and from moment.js to plain Date. Commit 5100758, +1,330/-5,011 lines

Most of these deletions (around 20,000 lines!) stem from package-lock.json files (or equivalent files from yarn or pnpm). This is amazing! This means that I probably have hundreds, if not thousands of dependencies less than when I started.

I don't know what the future will bring, but I'm pretty sure that I will have to do less work to keep these projects running. If you have small Node.js projects that don't benefit from dedicated test runners, I recommend you give node:test a try.