DEV Community

Cover image for NodeJS: We can run tests natively!
Gemma Black
Gemma Black

Posted on • Originally published at gemmablack.dev

NodeJS: We can run tests natively!

Once upon a time, there was Mocha.

Then there was Jasmine. Or perhaps it was the other way around. But Mocha was first for me.

There was Jest.

Now there's Vitest.

If you want to write tests for your Nodejs app, you're spoilt for choice. You just run npm install for the pleasure.

Some worked well with Grunt and Gulp.

Others worked great with Webpack.

Now there are those that work perfectly with Vite.

But unless we're doing some compilation for the front end or we're working with Typescript, why aren't we using NodeJS's native test runner? It's been around since version 18! That's April 2022. Almost 2 years in!

So what does it look like? 💯 zero node modules required:

And straight from the docs.

// > node test.mjs
import test from 'node:test';
import assert from 'node:assert';

test('synchronous passing test', (t) => {
    assert.strictEqual(1, 1);
});
Enter fullscreen mode Exit fullscreen mode

That's it!

Assertions? They're native.

Test runners? Also native.

The output? Delightful. 🤩

✔ synchronous passing test (0.618667ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 4.6255
Enter fullscreen mode Exit fullscreen mode

We can even run tests spanning multiple files. As long as the file ends with an acceptable glob pattern like *.test.mjs , you can run the following:

node --test
Enter fullscreen mode Exit fullscreen mode

Default globs are:

  • **/*.test.?(c|m)js

  • **/*-test.?(c|m)js

  • **/*_test.?(c|m)js

  • **/test-*.?(c|m)js

  • **/test.?(c|m)js

  • **/test/**/*.?(c|m)js

We even have watch mode for goodness' sake. How did I not know about this?

node --test --watch
Enter fullscreen mode Exit fullscreen mode

Code coverage? Yes. 😯

# node --test --experimental-test-coverage

ℹ tests 6
ℹ suites 2
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54.586833
ℹ start of coverage report
ℹ --------------------------------------------------------------
ℹ file          | line % | branch % | funcs % | uncovered lines
ℹ --------------------------------------------------------------
ℹ test.mjs      | 100.00 |   100.00 |  100.00 | 
ℹ test.test.mjs | 100.00 |   100.00 |  100.00 | 
ℹ --------------------------------------------------------------
ℹ all files     | 100.00 |   100.00 |  100.00 |
ℹ --------------------------------------------------------------
ℹ end of coverage report
Enter fullscreen mode Exit fullscreen mode

describe/it blocks? Yes, too! 🤯

describe('A thing', () => {
    it('should work', () => {
        assert.strictEqual(1, 1);
    });

    it('should be ok', () => {
        assert.strictEqual(2, 2);
    });

    describe('a nested thing', () => {
        it('should work', () => {
            assert.strictEqual(3, 3);
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

And it looks beautiful may I add.

â–¶ A thing
  ✔ should work (0.082667ms)
  ✔ should be ok (0.051292ms)
  â–¶ a nested thing
    ✔ should work (0.05575ms)
  â–¶ a nested thing (0.126541ms)

â–¶ A thing (0.512584ms)
Enter fullscreen mode Exit fullscreen mode

So why aren't we using more of Nodejs natively?

One reason is that the test runner didn't exist until April 2022, probably when I was already super excited about Vite. Another reason is we rarely just work with pure Nodejs. Normally we work with a frontend framework, be it React or Vue, Typescript, Purescript, or a fully-fledged framework like AdonisJS to make our life easier.

Reducing our dependencies and relying on native

Having recently fallen foul of dependency hell with an old project, I came to the realisation, that reducing dependencies makes it easier to update a project. Try and fix a Node version 8 script with a dozen node modules that are no longer maintained, and you realise it's probably easier to just write the whole thing again.

The downside is native Nodejs will be more verbose to write, it will lack the Typescript types that I love so much, and it will be missing the prettier documentation that comes with beautiful frameworks like Nuxt.

However, if I want my projects to last a long time with a minimum of fuss and maintenance, it might be worth forgoing these shinier tools that abstract away NodeJS's NodeJSness, and just use it with its fullest purity.

It's not just a NodeJS problem. Every language has this problem. We've developed tools to make our lives easier because changing and improving the underlying language isn't always possible.

Yet, Ricardo Lopes proved you can have a sane zero-dependency mini web application without even a package.json written in NodeJS:

"There's a place for complex frameworks and architectures, sure. But for many projects, they may be an overkill."

So if we're developing a project that's 1) small, 2) not really going to change much, and 3) only needs NodeJS, why not avoid using any dependencies at all, and go native with node --test?

I'll sign off with a Flavio Tweet in favour of reducing dependencies.

Top comments (0)