What's New on Node.js Test Runner and Why it's Game-changing

Rate this content
Bookmark

Node's new test runner is pretty cool but not many people are using it yet. In my talk, I'll show you all the neat stuff it can do, including some features I worked on. We'll take a look under the hood of Node to see how mocks work and how to use them. I'll also chat about what's next for the runner and what to expect down the line. Get ready to up your testing game with native assertions and keep things running fast!

Lucas Santos
Lucas Santos
17 min
04 Apr, 2024

Video Summary and Transcription

The Node.js Test Runner is presented as a better alternative to Jest, offering more flexibility and improved performance. It supports TypeScript out of the box and provides comprehensive test suite visualization. The test runner has native support for code coverage and upcoming features include module mocking and improved filtering. Shifting to the test runner is simple and helps the community grow.

Available in Español

1. Introduction to Node.js Test Runner

Short description:

Hello, everybody. Today, we're going to talk about the Node.js Test Runner. I'm Luca Santos, a senior software engineer at OpenVault. You can reach out to me on Twitter and Instagram at elsantos.dev.

Hello, everybody. Today, we're going to talk about the Node.js Test Runner. So first of all, I'm going to introduce myself. My name is Luca Santos. I am one of the senior software engineers there at OpenVault today. I live in Sweden. I'm originally from Brazil. And you can reach out to me on any of my social networks. So basically, there's the ones in the bottom there. It's Twitter.elsantos.dev, Instagram.elsantos.dev. You know, you get the idea. Luca is a very common name in Brazil. So basically, I had to, you know, choose DNS over Nix in social networks. So just put the social network name followed by .elsantos.dev. Or if you are in doubt on any social networks that you want to talk to me, just .elsantos.dev is going to take you to my main page where everything is. Okay? I'm super open to talk about anything. So just reach out to me, and it's going to be super nice.

2. The Benefits of the Node.js Test Runner

Short description:

My goal with this talk is to make you ditch Jest for something better, the Node.js test runner. Jest is outdated and has limitations. The Node.js test runner is actively improved and offers more flexibility. The current test runners are difficult to configure, especially for TypeScript, and often require additional libraries that can slow down your project. They also lack interoperability and can be inflexible.

Next up, I'm going to share my goal in this talk for you. Like, what is the goal that I have with this talk? It's to make you ditch Jest in the end. That is simple as that. I don't hate Jest. I actually use it a lot. But I had my fair share of problems. And I think it's time to move on, right? I think it's time that we need something new, something better. Jest is super old, right? It's very good, but the time has passed, right?

So I'm going to do this. I'm going to present you something that is possibly better. But why possibly better? Well, because the Node.js test runner is still being actively improved. So there's a bunch of things that are happening. There are a bunch of things that we already have, but there's still a bunch of things missing. So you can help there if you want, okay?

But first, what is bad about the current environment? Why do we need another test runner? Isn't today enough? Isn't what we have today enough? So basically, the first thing is that they're too difficult to configure, especially for TypeScript. And they do a lot of things. Like configuring Jest is a pain. It's a super big configuration file. They do a lot of stuff. They do transforming, they do parsing, they do moving, they assert, they have coverage. It's just too many stuff. And this makes them slow, right? Because, well, it's a lot of stuff. And then you have a concept that they call yall, which is basically yet another library on top of your project. Because runners, in my opinion, should be a thing from your runtime. You shouldn't be able to install, you shouldn't install another library to have a runner. And remember, every other library, every package that install on top of your project is a code that you don't maintain, but yet you rely on it, right? So if something happens, you're gonna pay the price. And test runners, they seem to not want to interoperate with each other. They make sure that you will only be able to use them and nothing else. So it's very difficult to change from one to the other. They're completely different stuff. Most of them are pretty opinionated. So they are either super extensible or not extensible at all.

3. Advantages of the Node.js Test Runner

Short description:

There is no middle ground or extensibility with existing test runners, especially for TypeScript. They either lack flexibility or require additional libraries. The Node.js test runner stands out as a built-in solution that is fast, easy to use, and works seamlessly with Node.js. It supports native assertions and can be integrated with other test processors. It provides consistent results across your project.

There is no middle ground or something that you can extend, or if you don't want to extend. So basically, it's difficult to, you know, do what you want unless you have something super extensible but yet super difficult to use like Jest or something that is not extensible at all. If you want TypeScript, like TypeScript is the nemesis of all test runners because they mess it up completely. And if you want TypeScript, you can either do like a TypeScript native test runner, which there are not many today, or you have another library on top of a project that you need to install to be able to run this, right, completely.

And there's so many of them, test runners in Spawnlight, RATS, there's a lot of them. And it's basically a huge bloat because they don't add a lot of things to the ecosystem. They don't interoperate with each other. They basically, they're just the same thing with minor improvements from one another. But then enters the Node.js test runner. The Node.js test runner is not that new. It was added by Colin in 2022, April 8, 2022. And then this was the initial version, like the CLI. And then one of my former colleagues in Microsoft, he added a TAP parser. TAP stands for test anything protocol. We're going to talk about it a bit later. But it allowed you to interoperate the test runner with other test runners, other actually test processors. Then it was soon stable. About four months later, it was made stable in Node 20. So you can actually use it in Node 20 and it's stable for production. It can use normally and freely.

But why is it different from the other ones and why am I advocating for it so much? First of all, it's a built-in, no YL, right? So not another library on top of a project. It is pretty fast, very fast, to be honest. And you don't need to configure anything. It's actually, there is no rituals that you need to do to configure it. It works seamlessly in Node.js. You can use native assertions if you want, but I reckon that the native assertion library is not the amazing library as others, but you can actually change the libraries to any other assertion library. It outputs the TAP protocol, so it's easy to integrate. It accepts other test reporters as well. You can use always the most up-to-date features of Node.js because it's always up-to-date with Node.js features. The best thing is that it has consistency across your project.

4. Running Tests with the Node.js Test Runner

Short description:

The Node.js test runner is built-in, easy to use, and works seamlessly with Node.js. It supports TypeScript out of the box and can be extended with other assertion libraries. Running tests is straightforward, and the output includes comprehensive information about the test suites, canceled tests, and skipped tests.

So you have thousands of projects, everyone's gonna be using the same test runner and the same Node.js test runner that is building the runtime, right? So you're gonna rely on the runtime itself. There is no polyfills that you need to do, like babble things and blah, blah, blah. And it has experimental support for code coverage and for now, it's becoming stable over the years and other reporters as well. And it supports kinda TS out of the box. It's very easy to make it run as you would run TypeScript in Node.js normally, right?

Let me show you how it works. First, we declare the function that we wanna test and then we create our test, right? So let's break that thing down a bit. So first we write the test. So we just import our libraries, we import the describe and it. You can use just like with a test directly. I prefer to use like this because I think it's pretty much easier to read. Then you import assertion function, the assertion library. It's the same that we used in other libraries. It's just a bit different, it's not that easy to read but I like to use the native one because it's native. And then we import our function that we wanna test and then the test is basically the same test that we would write on any other test runner. So describe the name of the test suite and then it and then the test that you wanna run and then you have assert and that's it basically. If you don't want to use the scribe or any other thing you can actually import it directly as test from no test. And then that's what I told you, it's a bit more verbose but works just the same. And the assertion libraries, they are taken care of by the assert library module, which is built in but I reckon this is really not the best one. I probably like to say that you should use it because it's native, but it's not the best library built in over there. So you can actually use anyone you want like Chai or other thing that you want to use. So basically just import it, the only thing you may have to make sure is that the assertion library throws instead when there's an error. When there's an error it throws and then boom, that's fine. And like I did here, I just imported Chai from, expect from Chai and then basically works normally. The important thing is that the library, the assertion library is completely untied to the runner. So you can actually run whatever you want, it's extensible enough to do so. And then when you run, it's basically running node dash dash test and the name of your test file. Node just append the test command on the node command and then run your test files. It's gonna give you a tap output. First, you can see everything, like you can see all your test suits and then you can see the cancel test, you can see the skip test, it's also a feature that is supported.

5. Mocking and Controlling Test Behavior

Short description:

Node.js test runner supports comprehensive test suite visualization, including cancel, skip, and to-do tests. It also provides powerful mocking capabilities for functions, getters, setters, and methods. Additionally, it offers built-in support for mocking timers and dates, allowing you to easily control and manipulate time-related functionality in your tests.

First, you can see everything, like you can see all your test suits and then you can see the cancel test, you can see the skip test, it's also a feature that is supported. You can see the to-do test that's also a supported feature.

And then we have mocks. Mocks is one of the best and most important features of a test, actually. For me, it's the best feature of Jest is the mocks. So, but node also has them. It's, node supports mocking some of the built-in structures. So like, mock Fn is just like Jest Fn, like mocks and functions, so just spy function. It also mocks getters, setters, and can actually mocks methods for now, right.

But there are also two other complex structures that I'm gonna take you through. The first of those structures is the date, actually, actually the timers. This was added by a very good friend of mine, Eric. And basically, it mocks things like setTimeout, setInterval, and stuff like this. So basically, what you need to do is just create your test, and then we create a function, which is a spy function, and then we enable the mock timers functionality, saying which library, which API we want to mock. So basically, setTimeout's the one that we want to mock. And then we just set a timeout for like 99999 something. And then we are gonna assert that the timer's not called yet because the timer hasn't run. So we have to take that timer to run, and then we can assert again and say, hey, now this time is called. And we can reset the timer back to its original state.

You can also import the mocked timers directly from the node test directory. You don't need to import it as in the context like we're using here, like context.mocks. You can just import it directly from the test. It works as well, basically like we are doing here. And the next complex structure was added by yours truly, and it's time, it's dates. So this is a feature that I've personally worked on and building on previous structures like the setTimeout and setInterval, it allows you to mock the date object. So mocking dates in Node is, the only thing that we have to do different is that we have to enable the mocks to use the date API. And then we start the mock. It's gonna start with the Unix epoch, so January 1st, 1970. And then we can tick the mock, and then we can assert that the new date is gonna be that date plus 9999 milliseconds. So it's something that's super useful if you wanna deal with like messaging and stuff.

6. Code Coverage and TypeScript Support

Short description:

The Node.js test runner has native support for code coverage, using the V8 built-in coverage function. You can change coverage reporters and report to different formats or files. TypeScript is fully supported, allowing you to run TypeScript files without any additional configuration. The test runner also offers features such as abort signals, skipping tests, flagging specific tests, life cycle hooks, and name pattern filtering. The future of Node.js testing includes upcoming support for module mocking in the test runner.

So we use it a lot. And code coverage is the next step for the supports that I want to tell you about. So it has, the Node.js test runner has a native support. You just pass on the flag, which is experimental test coverage. It's gonna use the V8 built-in coverage function to give you an output like this. So it's gonna give you, this is the command that we are gonna run, and this is gonna be the tests that are gonna run. And then we have the usual TAP output that we have on the side, and then we have the start of the coverage report. As you can see, in the coverage result, we have three, two lines that were not covered, which are the ones that we throw here in our function. So it works as expected, right? And then you can also change the coverage reporters. You can use different reporters, and or reporting directly to files, using the test reporter or the test reporter destination. And right now the support, the native test runner supports these reporters. So you can use spec.tap, lcol, and JUnit reporters as well, natively, just put them in the test reporter, equals, and the name of the reporter.

But now let's talk about TypeScript, which is the thing that I love the most. So a bit of a side here, something on the side, but I don't know if you know all of this, but Node already supports you to compile TypeScript on the fly using importers, right? So importers were called loaders previously, and they are functions that execute before a module is loaded and they can also be used to transform a module before it is imported. So Babel has loaders, Jest has loaders, a lot of other tools that we use has loaders because you can import the module and then it's going to change the file that you're in, and then it's going to execute the file that you're in, right? So this means that you can also basically run TypeScript because you can just import a TypeScript file and then convert this to JavaScript, right? And then the common importers that we have today, there are ts-node, ts-sex, and ts-build. You can also build your own if you want, if you don't want to use any external libraries. I like ts-sex, I think it's one of the best ones out there. And for the TypeScript support, I've just changed the file that we had before. I just added some types, and then we can run it with import ts-sex and basically the ts file index.ts, and then boom, magic, right? But what if I told you that you can actually use this to run the test runner? So the TypeScript support, let's just take our previous file extension and we just append dash dash test to it. So boom, it runs, nothing else, like, it's magic. That's run, everything runs, there's no configuration, no nothing, we just run with native stuff, right? And when you look at this, like, 50 line configuration jazz file that you've been maintaining for this, you know, past five years, you're going to see that this is quite of a bliss because you don't have to maintain TypeScript anymore, so it's super, super nice. And there are other supported features for a node test runner so you can actually abort tests via abort signals. You can skip the test, like I said, the it skip or it to do to test that is still to be done. You can flag one test that you want to run, so with it only, it's funny, super nice. And we also have life cycle hooks like Jess does with before, after, before each, after each and stuff like this. And you can also filter the name pattern via rejects. Now you can actually do, I think Node 21 has added the support for globs, so you can actually add globs in the file names and you have a watch mode, which is experimental. But the future of Node.js testing, and funny enough, Colin himself has a list of what he wants for the Node.js test runner. So the first thing I wanted to show you is the module mocking. So basically the module mocking is something that is coming up in the test runner as well.

7. Upcoming Features and Conclusion

Short description:

The upcoming features in the test runner include module mocking, improved filtering with globs, snapshot testing, source map support, and more mocks. You can get involved by contributing or providing feedback. Shifting to the test runner is simple and helps the community grow. Connect with me on social media for more information.

It's a bit different from what I would expect, but the idea that you can actually mock the whole module and not just mock the methods or files is super cool. We also have improved filtering, allowing you to run tests based on test name patterns with globs. Snapshot testing, which was briefly added in the past but removed due to storage issues, is something that we want to bring back. Source map support is self-explanatory, and we aim to create more mocks for different functionalities, such as performance mocks. While these features are still in development, you can get involved by contributing to the documentation or code, or simply using the test runner and providing feedback. Shifting your projects to the test runner is simple, and it helps the community grow.

Thank you so much for your time today. If you want to connect with me, you can find me on my social networks. To access the QR code or link for this talk, please refer to the information I'm leaving here. I hope you enjoyed the talk and see you soon.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

How Bun Makes Building React Apps Simpler & Faster
React Day Berlin 2022React Day Berlin 2022
9 min
How Bun Makes Building React Apps Simpler & Faster
Bun’s builtin JSX transpiler, hot reloads on the server, JSX prop punning, macro api, automatic package installs, console.log JSX support, 4x faster serverside rendering and more make Bun the best runtime for building React apps
Node.js Compatibility in Deno
Node Congress 2022Node Congress 2022
34 min
Node.js Compatibility in Deno
Can Deno run apps and libraries authored for Node.js? What are the tradeoffs? How does it work? What’s next?
Bun, Deno, Node.js? Recreating a JavaScript runtime from Scratch - Understand magic behind Node.js
Node Congress 2023Node Congress 2023
29 min
Bun, Deno, Node.js? Recreating a JavaScript runtime from Scratch - Understand magic behind Node.js
Bun, Deno, and many other JavaScript runtimes have been hyped, but do you know why? Is it that easy to make a runtime from scratch?

I've been researching the secret behind Node.js' power and why there are so many new JavaScript runtimes coming up. Breaking down each key component used on Node.js I've come to interesting conclusions that many people used to say whereas in practice it works a bit differently.

In this talk, attendees will learn the concepts used to create a new JavaScript runtime. They're going to go through an example of how to make a JavaScript runtime by following what's behind the scenes on the Node.js project using C++. They'll learn the relationship between Chrome's V8 and Libuv and what makes one JavaScript runtime better than others.

This talk will cover the following topics:
- What's a JavaScript Engine - V8
- Why Node.js uses Libuv
- How to create a JS Runtime from scratch
Eval all the strings! - Hardened JavaScript
Node Congress 2023Node Congress 2023
8 min
Eval all the strings! - Hardened JavaScript
This talk is about SecureEcmaScript and Compartments which are TC39 proposals, and I'm working on tooling to make these concepts usable with people championing those proposals.
This is a first-hand account of the future of JavaScript security.
SES + tooling (LavaMoat or Endo) is making limiting access to network, fs, core modules or globals possible on a per-package basis.
I want to show how they work, what possibilities they open and how to make that future happen today with some effort.
To me this is the final step in securing npm supply chain - even if a package gets taken over by bad actors, it won't be able to hurt me.
The Future of JavaScript Runtimes
Node Congress 2022Node Congress 2022
34 min
The Future of JavaScript Runtimes
JavaScript was born in the browser, Node brought it to the server embracing unix primitives and async I/O and lately Cloudflare Workers & Deno Deploy have brought it to the edge. Let’s take a look at where JavaScript runtimes are heading and how it will shape the software we write.
Roll you own JavaScript runtime
Node Congress 2023Node Congress 2023
21 min
Roll you own JavaScript runtime
In this talk, we’ll create a small JavaScript runtime from scratch in Rust. We’ll leverage the same ecosystem of components that Deno uses to show that rolling a bespoke runtime fitting your needs is simple and fun in 2023