Adonis.js is one of the fastest-growing Node.js frameworks. As stated on its homepage, the framework is designed for fans of test-driven development (TDD). As a feature of this design, it comes bundled with a specialized testing framework.

In this tutorial, you will learn how to automate the testing of an Adonis.js API with continuous integration so that your tests will run every time there is a change to your codebase.

Prerequisites

To follow this tutorial, a few things are required:

  1. Basic knowledge of Javascript
  2. Node.js installed on your system (>= 8.0)
  3. Adonis.js CLI installed globally
  4. A CircleCI account
  5. A GitHub account

Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.

With all these installed and set up, let’s begin the tutorial.

Cloning the Adonis.js API project

To get started, you need to clone the API project. In your terminal, run:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/adonisjs-api-testing.git

This clones the base-project branch of the repository into the location you ran the command in. This is the starting point of the API project. It has no tests set up yet.

Next, go to the root of the project and install the required dependencies:

cd adonisjs-api-testing
npm install

Then, create a new file at the root of the project with the name .env (note the dot .), and paste in this configuration:

PORT=3333
HOST=127.0.0.1
NODE_ENV=development
APP_KEY=2NdV4b7HHT2kBWLivPVOkdRhcHYumwcb
DRIVE_DISK=local
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis
HASH_DRIVER=bcrypt

This config specifies that this is a development environment and that you will be using a SQlite database named adonis for persistent storage. To get your database and database schema set up, create the database folder. Run the Adonis.js project migrations:

mkdir tmp && touch tmp/db.sqlite3

node ace migration:run

You have now set up your application and can take it for a spin. Boot up the application by running:


node ace serve --watch

This command starts the API running at http://127.0.0.1:3333. The port might be different if 3333 is already in use on your system.

Our API is a simple User accounts API. As seen in the ./start/routes.ts file, it contains two main endpoints.

// start/routes.ts

import Route from "@ioc:Adonis/Core/Route";

Route.get("/", async () => {
  return { greeting: "Welcome to the Adonis API tutorial" };
});

//User api routes
Route.group(() => {
  Route.post("user", "UsersController.create");

  Route.get("users", "UsersController.fetch");
}).prefix("api");

The user route calls the create method of UsersController to create a new user by supplying it with a username, email, and password. You also have a users endpoint that simply calls the fetch function of UsersController to return an array of users in the database. The implementation of these controller functions can be found in the app/Controllers/Http/UsersController.ts file:

// app/Controllers/Http/UsersController.ts

import type { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";

import User from "App/Models/User";

export default class UsersController {
  public async create({ request, response }: HttpContextContract) {
    try {
      const user = new User();
      user.username = request.input("username");
      user.email = request.input("email");
      user.password = request.input("password");

      let create_user = await user.save();

      let return_body = {
        success: true,
        details: create_user,
        message: "User Successully created",
      };

      response.send(return_body);
    } catch (error) {
      return response.status(500).send({
        success: false,
        message: error.toString(),
      });
    }
  } //create

  public async fetch({ request, response }: HttpContextContract) {
    try {
      const users = await User.query();

      response.send(users);
    } catch (error) {
      return response.status(500).send({
        success: false,
        message: error.toString(),
      });
    }
  } //fetch
}

Testing the API with Postman

Now put your API to the test by calling the endpoints. You will be using Postman to test the endpoints. Make sure that your app is running. If not, run node ace serve --watch again.

Testing user creation

User creation - successful

Testing user fetch

User fetch

Setting up the testing framework

AdonisJS provides out-of-the-box support for testing and doesn’t require any third party packages. At the root of your project, run the test using:

node ace test

You will get an output similar to this:

tests/functional/hello_world.spec.ts
  ✔ display welcome page (24ms)

 PASSED

Tests   : 1 passed (1)
Time    : 29ms

Before adding a new test suite for your API, replace the content of .env.testing with this configuration:

HOST=127.0.0.1
PORT=4000
NODE_ENV=test
APP_NAME=AdonisJs
APP_URL=http://${HOST}:${PORT}
CACHE_VIEWS=false
APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=adonis
HASH_DRIVER=bcrypt

Next, ensure that all tables in the database are truncated after each run cycle. Open the tests.bootstrap.ts file and update the runnerHooks method like this:

export const runnerHooks: Pick<Required<Config>, "setup" | "teardown"> = {
  setup: [() => TestUtils.ace().loadCommands(), () => TestUtils.db().truncate()],
  teardown: [],
};

Adding tests

Now it’s time to start adding some proper tests to your API project. You will be adding tests to test the api/user and api/users endpoints. To create a test suite for your tests, run:

node ace make:test functional User

A new file for the test suite will be created at test/functional/user.spec.ts. In this file, replace the contents with this code:

// test/functional/user.spec.js
import { test } from "@japa/runner";

test.group("User", () => {
  // Write your test here
  const mockUserData = {
    username: "Sample user",
    email: "user@sample.com",
    password: "12345678",
  };

  test("can create a new user", async ({ client }) => {
    const response = await client.post("/api/user").form(mockUserData);

    response.assertStatus(200);
    response.assertBodyContains({
      details: { username: "Sample user", email: "user@sample.com" },
      message: "User Successully created",
    });
  });

  test("can retrieve list of users", async ({ client }) => {
    const response = await client.get("/api/users");

    console.log(response.body());

    response.assertStatus(200);
  });
});

AdonisJS ships with the Japa API client plugin for testing API endpoints over HTTP. With it, you can access the client property for calling your endpoints.

In the first test suite, you created a new user by calling the api/user endpoint with the appropriate data, and asserting that 200 response code was returned, indicating a successful operation.

Next, you created another test suite to retrieve the list of users with an HTTP call to the api/users endpoint. The response is then tested to ensure that it returns a 200 status code.

Time to run all the tests contained in the tests folder. To do that, run the test command once again:

node ace test

You will get an output like this:

tests/functional/hello_world.spec.ts
  ✔ display welcome page (26ms)

functional / User (tests/functional/user.spec.ts)
  ✔ can create a new user (129ms)
[
  {
    id: 1,
    username: 'Sample user',
    email: 'user@sample.com',
    created_at: '2023-10-02T02:08:03.000+01:00',
    updated_at: '2023-10-02T02:08:03.000+01:00'
  }
]
  ✔ can retrieve list of users (4ms)
[ success ]  Truncated tables successfully

 PASSED

Tests   : 3 passed (3)
Time    : 169ms

Connecting the API project to CircleCI

Your next task is to get your project set up on CircleCI. Begin by creating a folder named .circleci at the root of your project. Now, create a new file named config.yml within the folder and use this content:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:18.18.0-browsers
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Create database folder
          command: mkdir tmp && touch tmp/db.sqlite3
      - run:
          name: Run Migrations
          command: |
            mv .env.test .env
            node ace migration:run
      - run:
          name: Run tests
          command: npm run test

This configuration starts by updating npm to ensure that you are using the lastest version. Next, it installs the required dependencies and caches them. The config then creates a database folder and sqlite3 file as temporary database for the project. Next, it creates an environment configuration file (.env) and runs your migrations. With all dependencies and database set up, your tests are run.

Save this file, commit, and push your changes to your repository to trigger the CI pipeline to run. review pushing your project to GitHub Review pushing your project to GitHub for help.

On your CircleCI dashboard, search for theadonisjs-api-testing project and click Set Up Project.

Add project - CircleCI

Specify the branch where your configuration file is stored and CircleCI will detect the config.yml file for the project. Click Set Up Project. Your workflow will start and run successfully.

Build successful - CircleCI

View your test results by clicking on the build.

Tests automated - CircleCI

Your tests are now running automatically when you push new code to your repository.

Conclusion

Test-driven development (TDD) is a best practice that many developers are still struggling to integrate into their development flow. Having a framework that has TDD built into its core makes adopting it much less cumbersome. In this tutorial, you learned to set up the testing of our Adonis.js API, and how to automate the process with CircleCI.

Happy coding!


Fikayo Adepoju is a LinkedIn Learning (Lynda.com) Author, Full-stack developer, technical writer, and tech content creator proficient in Web and Mobile technologies and DevOps with over 10 years experience developing scalable distributed applications. With over 40 articles written for CircleCI, Twilio, Auth0, and The New Stack blogs, and also on his personal Medium page, he loves to share his knowledge to as many developers as would benefit from it. You can also check out his video courses on Udemy.

Read more posts by Fikayo Adepoju