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 so that your tests will run on every 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
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 that we will be testing. In your terminal, run the following command to clone the base project:
git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/adonis-api-testing.git
This will immediately clone the base-project
branch of the repository into the location in which you have run the above command. This is the starting point of the API project. It has no tests setup.
Next, go into the root of the project and install the required dependencies:
cd adonis-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 the following configuration:
HOST=127.0.0.1
PORT=3333
NODE_ENV=development
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
The above config specifies that we are in a development environment and that we will be using a SQlite
database named adonis
for persistent storage. To get our database and database schema setup, run the Adonis.js project migrations with the following command:
adonis migration:run
We have now completely set up our application and can take it for a spin. Boot up the application by running the following command:
adonis serve --dev
This command will start 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.js
file, it contains two main endpoints.
// start/routes.js
"use strict";
const Route = use("Route");
Route.get("/", () => {
return { greeting: "Welcome to the Adonis API tutorial" };
});
//User api routes
Route.group(() => {
Route.post("create", "UserController.create");
Route.route("get", "UserController.fetch", ["GET", "POST"]);
}).prefix("user");
The create
route calls the create
method of UserController
to create a new user by supplying it with a username
, email
, and password
. We also have a get
endpoint that simply calls the fetch
function of UserController
to return an array of users in the database. The implementation of these controller functions can be found in the app/Controllers/Http/UserController.js
file shown below.
// app/Controllers/Http/UserController.js
async create({ request, response }) {
const data = request.post();
const rules = {
username: `required|unique:${User.table}`,
email: `required|unique:${User.table}`,
password: `required`
};
const messages = {
"username.required": "A username is required",
"username.unique": "This username is taken. Try another.",
"email.required": "An Email is required",
"email.unique": "Email already exists",
"password.required": "A password for the user"
};
const validation = await validate(data, rules, messages);
if (validation.fails()) {
const validation_messages = validation.messages().map((msgObject) => {
return msgObject.message;
});
return response.status(400).send({
success: false,
message: validation_messages
});
}
try {
let create_user = await User.createUser(data);
let return_body = {
success: true,
details: create_user,
message: "User Successully created"
};
response.send(return_body);
} catch (error) {
Logger.error("Error : ", error);
return response.status(500).send({
success: false,
message: error.toString()
});
}
} //create
async fetch({ request, response }) {
const data = request.all();
try {
const users = await User.getUsers(data);
response.send(users);
} catch (error) {
Logger.error("Error : ", error);
return response.status(500).send({
success: false,
message: error.toString(),
});
}
} //fetch
Testing the API with Postman
Let’s now put our API to the test by calling our endpoints. We will be using Postman to test our endpoints. Make sure that your app is running. If not, run adonis serve --dev
once again.
Testing user creation
Testing user fetch
Setting up the testing framework
The next step is to set up the testing framework for testing Adonis.js applications. Fortunately, Adonis.js has its own specialized testing package known as Vow. Install Vow
with the following command:
adonis install @adonisjs/vow
The installation of this package will cause the following changes to your project:
- A
vowfile.js
file will be created at the root of your project. - A
test
folder will be created at the root of the project. This is where all of the tests will be contained. In order to organize tests, all unit tests are placed inside aunit
folder within this folder. Functional tests are placed inside afunctional
folder. By default, a sample unit test suite is created insidetest/unit/example.spec.js
. - A
.env.testing
file is created at the root of the project to contain environment variables specific to testing purposes. This file gets merged with.env
, so you only need to define values you want to override from the.env
file.
Replace the content of .env.testing
with the configuration below:
HOST=127.0.0.1
PORT=4000
NODE_ENV=testing
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
The final step to complete our test setup is to add the Vow
provider to our project. In the ./start/app.js
file, add the following item to the aceProviders
array:
...
const aceProviders = [
....,
"@adonisjs/vow/providers/VowProvider"
];
...
Run the sample test that came with the Vow
installation by running the following command:
adonis test
The sample test will run successfully.
Adding tests
It is now time to start adding some proper tests to our API project. We will be adding tests to test our create
and get
endpoints. Run the following command to create a test suite in which we will put our tests:
adonis make:test User
In the options that display after running this command, select Functional test
(using the arrow keys) and press Enter
.
A new file for the test suite will be automatically created at test/functional/user.spec.js
. In this file, replace the contents with the following code:
// test/functional/user.spec.js
"use strict";
const { test, trait, after } = use("Test/Suite")("User");
const User = use("App/Models/User");
trait("Test/ApiClient");
const randomString = generateRandomString();
test("Test User creation", async ({ client }) => {
const userData = {
username: randomString,
email: `${randomString}@test.com`,
password: "123456"
};
const response = await client.post("/user/create").send(userData).end();
response.assertStatus(200);
}).timeout(0);
test("Fetch all users", async ({ client }) => {
const response = await client.get("/user/get").end();
response.assertStatus(200);
response.assertJSONSubset([
{
username: randomString,
email: `${randomString}@test.com`
}
]);
}).timeout(0);
//Delete the created user
after(async () => {
await (await User.findBy("username", randomString)).delete();
});
function generateRandomString(length = 7) {
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
In the above test suite, the Adonis.js Test/ApiClient
trait is used to gain access to the client
object, which is used to call our endpoints. The generateRandomString
random number generator function is used to create a fake username and email for a test user.
In the Test User creation
test, we create a new user by calling the create
endpoint with the appropriate data, and checking if we get a 200
response code that indicates that the operation was successful.
In the Fetch all users
test, the client
object is used to call our get
endpoint. The response is then tested to ensure that it returns a 200
status code. We also check to see if it returns an array containing the user we created in the previous test.
We attach .timeout(0)
to each test to override the timeout limit for the test runner as functional and browser tests might take a while to run.
Finally, we do some clean up by deleting the test user we created. This is done after both tests have completed.
Time to run all the tests contained in the tests
folder. To do that, run the test command once again:
adonis test
Connecting the API project to CircleCI
Our next task is to get our project set up on CircleCI. Begin by pushing your project to GitHub.
Next, go to the Add Projects page on the CircleCI dashboard to add the project.
Click Set Up Project.
On the setup page, click Add Manually to instruct CircleCI that we would be adding a configuration file manually and not using the sample displayed. Next, you get a prompt to either download a configuration file for the pipeline or start building.
Click Start Building to begin the build. This build will fail because we have not set up our configuration file yet, this we will do later on.
Automating our tests
Now that we have our project connected to CircleCI, we can write a configuration for our continuous integration (CI) pipeline that will automate our testing process.
At the root of your the project, create a folder named .circleci
, and a file within it named config.yml
. Inside the config.yml
file, enter the following code:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/node:11-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: Run Migrations
command: |
mv .env.testing .env
node ace migration:run
- run:
name: Run tests
command: npm run test
In the configuration file above, we start by updating npm
to ensure that we are using the lastest version. Next, we install the required dependencies and cache them. We then create our environment configuration file (.env
) and run our migrations. With all dependencies and database set up, we run our tests.
Save this file, commit, and push your changes to your repository to trigger the CI pipeline to run.
Let’s view our test results by clicking on the build to see behind the scenes.
Our tests are now running automatically when we push new code to our repository.
Conclusion
Test-driven development (TDD) is a best practice that a lot of developers are still struggling to integrate into their development flow. Having a framework that has TDD built into its core really makes adopting it less cumbersome. In this tutorial, we 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.