< Back

A pretty good testing pattern

Testing really isn't simple or easy. A lot of frameworks trick us into thinking it is. That's great when they work, but can be very frustrating when they don't. And if they have 1.5k open issues (at the time of this writing), I personally find that it shakes my confidence and can really put a dent in a workday.

I think there are a lot of good test runners out there. I've used a lot of them. With Node's transition to ESM and syntaxes like JSX and Typescript, getting everything configured perfect is still kinda tough sometimes.

My preferred pattern atm

At low level, a test is essentially a Node script that makes some assertions and exits 1 if any fail. So I've been trying to stay close to that.

node test.js

I've been using esbuild for building my libraries, and esbuild-register makes it easy to keep the vanilla command line and still load non-standard syntax.

node -r esbuild-register test.ts

You probably also want a small library for test suites to help label, structure, and assert your test suites. uvu is currently my pick. It's small, but has most of the ergonmics of much larger libraries.

Coverage is important, and luckily c8 makes that a breeze as well. Uploading and processing coverage is as easy as configuring the Coveralls GitHub Action.

c8 -r lcov node -r esbuild-register test.ts

For fixtures, I've always liked tap's pattern. So recently I made my own (very tiny) take on it, afix.

And mocks, the only thing I've found that works for everything is still proxyquire.

Example

Here's the full test suite for afix.

import fs from 'fs'
import { test } from 'uvu'
import * as assert from 'uvu/assert'

import { afix } from '../'

test('afix', async () => {
  const fixture = afix({
    a: ['a.js', 'export default "foo"'],
  })

  assert.ok(fs.existsSync(fixture.files.a.path))

  fixture.cleanup()
})

test.run()
c8 -r lcov node -r esbuild-register lib/__tests__/index.ts

Notes

Smaller libraries mean fewer bugs and faster more efficient runtime. It can also mean lack of features, but the flexibility of combinging smaller options like these means you get the features and ultimate control.

It's a give and take but I think I'm finally pretty content with my testing setup.

Would love anyone's thoughts, suggestions, alternatives, etc — talk to me on Twitter.