Continuous integration for AdonisJS APIs
Fullstack Developer and Tech Author
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:
- Basic knowledge of Javascript
- Node.js installed on your system (>= 8.0)
- Adonis.js CLI installed globally
- A CircleCI account
- 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
Testing 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.
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.
View your test results by clicking on the build.
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!