Set up your Node project to run tests locally and on CircleCI

You're creating the backend API for your new Node.js service and it's come time to setup Continuous Integration / CI so you can actually deploy the service.

CI pipelines can handle a lot of different tasks (building, linting, running tests, checking dependencies, publishing the package if it's a module, etc).

But here we'll just focus on setting up your project to be able to run tests on CI - using CircleCI as our job runner.

Configuring CircleCI to run tests

In order for CircleCI to be able to run tests, it needs to know what script to run to actually run the tests. There are multiple ways you can do this, but I like using a package.json script.

In package.json, add the following:

"test:ci": "NODE_ENV=test mocha ./tests/**/*.test.js",

The glob pattern for your tests - the ./tests/**/*.test.js from above - may differ, and you might be using Jest over Mocha, or using different Mocha args, but the gist is the same.

Also, for your tests if you want to leverage import statements instead of require for your modules/dependencies, check out how to do that here.

The most important thing to note from above is that we've named the script test:ci and not just test.

Why test:ci and not just test?

I like having the flexibility of running my tests locally differently - for example, being able to run the tests in --watch mode so that everytime my code changes, the tests run again. Very useful for TDD!

The problem with that is, if we run the tests in watch mode on CI, they will never exit, and eventually the CI job will time out.

So by adding another CI-specific test script, we can have CircleCI leverage test:ci.

And then we can have another script to run tests locally, like so:

"test": "NODE_ENV=test mocha --watch ./tests/**/*.test.js"

CircleCI config

Now, for configuring CircleCI to run those tests...

Create a .circleci folder in the root of your project, and add a config.yml file to it.

In the config.yml file, copy and paste the following:

version: 2

defaults: &defaults
  working_directory: ~/repo
  docker:
    - image: circleci/node:10.14.2

jobs:
  core:
    <<: *defaults
    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: npm ci

      - run: npm run test:ci

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}

      - persist_to_workspace:
          root: ~/repo
          paths: .

workflows:
  version: 2
  core:
    jobs:
      - core:
          filters:
            branches:
              only:
                - master

Now, whenever you push a change to your master branch, CircleCI will run the core job configured in config.yml, which will install the dependencies for your project then run the tests!

If those tests finish successfully, then your project has run in an isolated, production-like environment and you've got confidence that it will run correctly in production!

Knowing how to setup CI is one hurdle... understanding how to structure your project is another. Want an Express REST API structure template that makes it clear where your logic should go, and configures basic CI for you as described in this post? Sign up below to receive that template, plus a post explaining how that structure works / why it's setup that way so you don't have to waste time wondering where your code should go. You'll also receive all my new posts directly to your inbox!

Subscribe for the template!

No spam ever. Unsubscribe any time.