Testing your API with Dredd
Usually, when applications are being developed, front-end and back-end developers are taking two separate paths on the route to implementation. Front-end developers are more design-driven, while back-end developers are more data-oriented. This often leads to a potential integration gap, where one team has certain expectations in terms of provided data, the structure of responses, etc, while the other implements something entirely different.
Introduction
In this article, we are going to present a technology stack aimed to bridge the gap between front-end and back-end developers, enable us to document the API and continuously test it once it’s implemented.
The stack presented in this article consists of the following:
- Dredd — API testing tool utilizing API Blueprint and Swagger API Description formats
- API Blueprint — Specification language allowing us to document our APIs in Markdown-like syntax
- Drakov — Tool that can use API Blueprint description of our API and set up a mock server to host the endpoints
Examples in this post will be shown using simple Node.js API with Express middleware on top of it.
Installation and setup
Dredd is based on Node.js so, before installing it, make sure you have Node.js and npm installed on your machine. It can be installed as an npm package with the following command:
> npm install -g dredd
Once the installation is complete, you could check if it’s installed properly by running:
> dredd --version
Simple example with API Blueprint description file
Let’s say we have an API with an endpoint to create a new user:
POST /api/users
which accepts a JSON request body containing email and password values:
{
"email": "testing@email.com",
"password": "pa55w0rd"
}
API Blueprint specification to test the following endpoint would look like this:
FORMAT: 1A# Dredd example## Users [/api/users]### Create User [POST]+ Request (application/json) {
"email": "test@email.com",
"password": "pa55w0rd"
}+ Response 200 (application/json; charset=utf-8) + Headers jwt-token: (string, required) + Body {
"id": 1,
"email": "test@mail.com",
"password": "pa55w0rd",
"provider": "local",
"role": "user"
}
There are two ways we can validate the API implementation against the Blueprint file.
Manual runs
Dredd enables us to run ad-hoc tests by specifying the name of the API blueprint file and the URL of the API:
> dredd api-description.apib http://localhost:9090
The command above assumes the API Blueprint file is called api-description.apib and your API is running on your local machine on port 9090. Values might be different depending on your setup.
Configured runs
There is also an easier way to setup Dredd, and that is by running > dredd init
command, which runs configuration wizard to help you create dredd.yml
file in your project root. Once you answer a few questions from the interactive wizard, you can run your tests by simply typing: > dredd
. If properly configured, Dredd will start the backend server process using the command you provided to the wizard and start testing.
In both cases, the output would be similar to this:
> dredd
info: Configuration './dredd.yml' found, ignoring other arguments.
warn:
Apiary API Key or API Project Subdomain were not provided.
Configure Dredd to be able to save test reports alongside your Apiary API project:
https://dredd.readthedocs.io/en/latest/how-to-guides/#using-apiary-reporter-and-apiary-testsinfo: Starting backend server process with command: npm run start
info: Waiting 3 seconds for backend server process to start> dredd-example@0.0.1 start /Users/code/dredd-example
> node server/app.jsExpress server listening on 9000, in development mode
info: Beginning Dredd testing...
pass: POST (200) /api/users duration: 55ms
complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 901ms
POST /api/users 200 4.167 ms - 151
complete: See results in Apiary at: https://app.apiary.io/public/tests/run/f1642892-a4eb-4970-8423-5db5c986a509
info: Backend server process exited
Since we haven’t specified any API keys, Dredd warns us that test runs will not be saved to our Apiary account. In this case, they are saved as public runs and kept for 24 hours, which is good enough for the purpose of this article. Let’s open our test run by using the URL from the output (note — your URL will differ): https://app.apiary.io/public/tests/run/f1642892-a4eb-4970-8423-5db5c986a509
In the Test Run Viewer, we are able to check each request from the test run, returned responses, differences and the outcome.
Using Hooks for setup and teardown
As many other testing frameworks, Dredd also supports adding hooks to run setup and teardown code, write custom expectations, handle authorization and share data between tests. Hooks can be written in a number of supported languages and, in this article, we’ll see how to add hooks in natively supported Node.js.
We begin by adding a hooks file to a project (in our example, we can add it to the project root and name it dredd-hooks.js).
There are two ways to make Dredd use a hooks file. One is to manually add a command argument with the path to our hooks file:
> dredd --hookfiles=dredd-hooks.js
The other way is to edit our dredd.yml file and update the configuration by setting the hookfiles property.
dry-run: null
hookfiles: dredd-hooks.js
language: nodejs
sandbox: false
server: npm run start
server-wait: 3
init: false
custom: {}
names: false
only: []
reporter: apiary
output: []
header: []
sorted: false
user: null
inline-errors: false
details: false
method: []
color: true
level: info
timestamp: false
silent: false
path: []
hooks-worker-timeout: 5000
hooks-worker-connect-timeout: 1500
hooks-worker-connect-retry: 500
hooks-worker-after-connect-wait: 100
hooks-worker-term-timeout: 5000
hooks-worker-term-retry: 500
hooks-worker-handler-host: 127.0.0.1
hooks-worker-handler-port: 61321
config: ./dredd.yml
blueprint: api-description.apib
endpoint: 'http://localhost:9000'
Now that we have the file, we can start writing the code around each transaction. Dredd identifies transactions by their name in the API Blueprint description file (.apib). To list transaction names during test runs, you can add --names
command argument: > dredd --names
.
In our example, we have a single transaction called Users > Create User
and that’s how we will reference it in our code.
Hooks are particularly important when we have quite a few endpoints in our API, and we don’t want to depend on any particular order by which they are executed. For instance, if we had an endpoint to delete a user, in order to test it in isolation (without depending on the Create User endpoint being run first), we would have to create a test user before the test is executed. Here’s how the hooks file could look like:
var hooks = require('hooks');
var User = require('../dredd-example/server/api/user/user.service');var testStash = {
newUserId: null
};hooks.before('Users > Delete User', function(transaction) {
hooks.log('Executing hook "before" transaction "Users > Delete User"');
User.save({
email: 'user@test.com',
password: '12345'
}, function(err, newUser) {
if (!err) {
hooks.log('New user created');
testStash.newUserId = newUser.id;
} else {
transaction.fail = 'Unable to create new user';
}
})
});hooks.after('Users > Delete User', function(transaction) {
hooks.log('Executing hook "after" transaction "Users > Delete User"');
// In case the actual test failed, we want to clean up the data
if (testStash.newUserId != null) {
User.delete(testStash.newUserId);
}
});
There are a few things to consider in the code above:
- We declared a new variable called
testStash
, which we used to preserve ID of the newly created user across multiple test hooks. - In the
before
hook, if we were unable to create the user, we can fail the test manually, by settingfail
property with a failure message. - In the
after
hook, we get the ID of the user from the stash, and use it to clean up after our test by deleting the user.
Setting up a mock server
Another cool feature when having an API documented in API Blueprint format is that we can also use the same file to start up a mock server to host our endpoints. This is particularly useful for front-end developers since they don’t have to wait for the API to be complete and deployed. Instead, they can use the .apib file to spin up the mocked server, integrate client apps with it and rest assured that the real API will also conform to the same specifications.
The tool for the job is called Drakov, and it can also be installed as an npm package by running:
> npm install -g drakov
Once the installation is complete, we just need to point Drakov to our API Blueprint file by typing:
> drakov -f api-description.apib
This command will run Drakov with default parameters and show the following output:
> drakov -f api-description.apib
[INFO] No configuration files found
[INFO] Loading configuration from CLI
DRAKOV STARTED
[LOG] Setup Route: GET / Get Message
[LOG] Setup Route: POST /api/users Create User
[LOG] Setup Route: GET /api/users/:id Get User Info
[LOG] Setup Route: DELETE /api/users/:id Delete User
Drakov 1.0.4 Listening on port 3000
Now we can perform any HTTP operation against the mocked API and start getting HTTP responses as defined in the document.
Final words
The tools presented today are simple and straightforward, but also very capable. They cover a lot of tasks, from documenting the API, testing the implementation and running mock servers for convenience.
Dredd has numerous options and can be configured with various types of requests. It can also be integrated with all major CI tools for repeated testing, which provides a great safety net for developers.
API Blueprint is a very expressive markdown-like format and can be used to describe almost every detail of the request and response.
Drakov is really straightforward and can be used out of the box by running a simple command.
All of this takes just a few hours to prepare and configure, and afterwards you will be able to say goodbye to your undocumented APIs.