TLDR

Test-driven Development

Test-driven development (TDD) is a software development process that focuses on a short development cycle that relies on automated tests. Here’s how we want to build our app each time we make a feature change:

Add a test

Run all tests to see if the new test fails

Write some code

Run all tests to see if the new test passes

Refactor

Repeat

The Test Database

Since our app interacts with a database, our tests will need their own database to interact with as well. We’ve already setup Knex.js to work with our database layer, so let’s add a test config to our knexfile. We’re also specifying separate seed directories for our different environments which will come into play later. We’re sticking to the Rails-like naming conventions for our databases.

Meet Jest

Just as there are in Rails, there are plenty of testing frameworks for Node to choose from. We’re going to use Jest, a super-popular simple test framework that works out of the box with Node. Let’s install it – both in our project and globally.

yarn add --dev jest
yarn global add jest

We can initialize Jest and answer a few questions about including coverage and adding Jest to our package scripts with another command. Answer y to all questions for now.

jest --init

This adds a jest.config.js file to our project and a test script to our package.json file.

Parallel Testing

By default, Jest will run all our tests in separate processes. Unfortunately, when dealing with a single test database, this can cause problems, as migrations interfere with each other.

Unit Testing

Unit testing covers the smallest pieces of our app. We want to ensure at a small scale that the fundamental elements work properly in isolation.

For our first unit test, we’re going to test our Article model to make sure every article in our blog has a title.

All Jest tests are expected to go in a __tests__ directory at the root of your project or be named with a .test.js extension. Like Rails we’re going to keep all our tests in a test directory at our project root, and stick to that naming convention.

Run the test, and watch it fail. We need to validate our article. You’ll note I called the $validate() method on the article. Similar to Rails ActiveRecord validation, Objection.js makes it super easy for us to provide validation criteria for all our models. Let’s edit our Article model to check for a title. We need to add a JSON Schema that describes our model.

Now we know our articles require a title. Run the test suite again, and watch it pass!

Integration Testing

Unit tests are great for isolating the granular pieces of your app, but our blog is a web application with lots of connected components – well, not so much yet, but it will be 😀. We need a way to test that the system works as a whole. To automate this, we require an abstract way to test integrated HTTP requests. Enter SuperTest.

yarn add --dev supertest

But first, we need to do a little refactoring so we can access our app from our tests. Copy index.js into a new app.js file, pull out a couple of lines, and add a module export at the bottom.

We already wrote these endpoints in our app, so we can run the test and watch it pass. This proves that the user can successfully reach the new article page, they can submit the form to create a new article, and will be redirected to its page.

Note that for a full end-to-end integration test you may want to consider a headless browser like Puppeteer. But in this case, our SuperTest integration suite will suffice.

Coverage

Finally, let’s take a look at how thorough we’ve been with our test coverage. Jest makes it super easy to view a coverage chart. Just add the flag to our test command.

Summary

Now that you’re equipped with the tools to build out your Node projects with TDD, go forth and make your app! There’s still plenty to do for our blog app: relational mapping, security, partials, debugging – the list goes on. Let me know what you’d like to see next. I’d also love to hear what you bring from your Rails background to Node.