Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations.
Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test.
Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases)
index.js- node entry that initializes our Fastify app and listens for incoming http requests on port 3001
app.js- this file exports a function that creates and returns our Fastify application instance
sql-plugin.js- a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance
A simple first test
For our first test we will just test our servers index route. If you recall from the
app.js code above, our index route returns a 501 response for “not implemented”.
In this test, we're using the
createApp function to create a new instance of our Fastify app, and then using the
inject method from the Fastify API to make a request to the
/ route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions.
To run this test, we can use the following command:
By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use.
Testing our SQL plugin
Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features.
In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter
t that we call methods on in our nested test structure. In this example, we use
t.beforeEach to create a new Fastify app instance for each test, and call the
test method to register our nested tests. Along with
beforeEach the other methods you might expect are also available:
Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s.
Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down.
A lot of other features that are common in other popular testing frameworks are also available.
Test styles and methods
Along with our basic
test based tests we used for our Fastify plugins -
test also includes
only methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests.
If you prefer, you also have the option of using the
it test syntax. They both come with the same methods as
test and I think it really comes down to a matter of personal preference.
This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really?
Other CLI flags
—test-name-pattern - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern
You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are
ts-node. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release.
The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support.
This is the test command that I ended up with in my application:
I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂