Hooks
Dredd supports hooks, which are blocks of arbitrary code that run before or after each test step. The concept is similar to XUnit’s setUp
and tearDown
functions, Cucumber hooks, or Git hooks. Hooks are usually used for:
Loading database fixtures,
cleaning up after test step(s),
handling auth and sessions,
passing data between transactions (saving state from responses),
modifying a request generated from the API description,
changing generated expectations,
setting custom expectations,
debugging by logging stuff.
Getting started
Let’s have a description of a blog API, which allows to list all articles, and to publish a new one.
FORMAT: 1A
# Blog API
## Articles [/articles]
### List articles [GET]
+ Response 200 (application/json; charset=utf-8)
[
{
"id": 1,
"title": "Creamy cucumber salad",
"text": "Slice cucumbers…"
}
]
### Publish an article [POST]
+ Request (application/json; charset=utf-8)
{
"title": "Crispy schnitzel",
"text": "Prepare eggs…"
}
+ Response 201 (application/json; charset=utf-8)
{
"id": 2,
"title": "Crispy schnitzel",
"text": "Prepare eggs…"
}
swagger: "2.0"
info:
title: "Blog API"
version: "1.0"
consumes:
- "application/json; charset=utf-8"
produces:
- "application/json; charset=utf-8"
paths:
"/articles":
x-summary: "Articles"
get:
summary: "List articles"
description: "Retrieve a list of all articles"
responses:
200:
description: "Articles list"
examples:
"application/json; charset=utf-8":
- id: 1
title: "Creamy cucumber salad"
text: "Slice cucumbers…"
post:
summary: "Publish an article"
description: "Create and publish a new article"
parameters:
- name: "body"
in: "body"
schema:
example:
title: "Crispy schnitzel"
text: "Prepare eggs…"
responses:
201:
description: "New article"
examples:
"application/json; charset=utf-8":
id: 2
title: "Crispy schnitzel"
text: "Prepare eggs…"
Now let’s say the real instance of the API has the POST request protected so it is not possible for everyone to publish new articles. We do not want to hardcode secret tokens in our API description, but we want to get Dredd to pass the auth. This is where the hooks can help.
Writing hooks
Hooks are functions, which are registered to be ran for a specific test step (HTTP transaction) and at a specific point in Dredd’s execution life cycle. Hook functions take one or more transaction objects, which they can modify. Let’s use hooks to add an Authorization header to Dredd’s request.
Dredd supports writing hooks in multiple programming languages, but we’ll go with JavaScript hooks in this tutorial as they’re available out of the box.
Let’s create a file called hooks.js
with the following content:
const hooks = require('hooks');
hooks.before('Articles > Publish an article', (transaction) => {
transaction.request.headers.Authorization = 'Basic: YWxhZGRpbjpvcGVuc2VzYW1l';
});
As you can see, we’re registering the hook function to be executed before the HTTP transaction Articles > Publish an article
. This path-like identifier is a transaction name.
Let’s create a file called hooks.js
with the following content:
const hooks = require('hooks');
hooks.before('Articles > Publish an article > 201 > application/json; charset=utf-8', (transaction) => {
transaction.request.headers.Authorization = 'Basic: YWxhZGRpbjpvcGVuc2VzYW1l';
});
As you can see, we’re registering the hook function to be executed before the HTTP transaction Articles > Publish an article > 201 > application/json
. This path-like identifier is a transaction name.
Running Dredd with hooks
With the API instance running locally at http://127.0.0.1:3000
, you can now run Dredd with hooks using the --hookfiles
option:
dredd ./blog.apib http://127.0.0.1:3000 --hookfiles=./hooks.js
dredd ./blog.yaml http://127.0.0.1:3000 --hookfiles=./hooks.js
Now the tests should pass even if publishing new article requires auth.
Supported languages
Dredd itself is written in JavaScript, so it supports JavaScript hooks out of the box. Running hooks in other languages requires installing a dedicated hooks handler. Supported languages are:
Note
If you don’t see your favorite language, it’s fairly easy to contribute support for it! Join the Contributors Hall of Fame where we praise those who added support for additional languages.
(Especially if your language of choice is Java, there’s an eternal fame and glory waiting for you - see #875)
Transaction names
Transaction names are path-like strings, which allow hook functions to address specific HTTP transactions. They intuitively follow the structure of your API description document.
You can get a list of all transaction names available in your API description document by calling Dredd with the --names
option:
$ dredd ./blog.apib http://127.0.0.1:3000 --names
info: Articles > List articles
skip: GET (200) /articles
info: Articles > Publish an article
skip: POST (201) /articles
complete: 0 passing, 0 failing, 0 errors, 2 skipped, 2 total
complete: Tests took 9ms
As you can see, the document ./blog.apib
contains two transactions, which you can address in hooks as:
Articles > List articles
Articles > Publish an article
$ dredd ./blog.yaml http://127.0.0.1:3000 --names
info: Articles > List articles > 200 > application/json; charset=utf-8
skip: GET (200) /articles
info: Articles > Publish an article > 201 > application/json; charset=utf-8
skip: POST (201) /articles
complete: 0 passing, 0 failing, 0 errors, 2 skipped, 2 total
complete: Tests took 9ms
As you can see, the document ./blog.yaml
contains two transactions, which you can address in hooks as:
Articles > List articles > 200 > application/json; charset=utf-8
Articles > Publish an article > 201 > application/json; charset=utf-8
Note
The transaction names and the --names
workflow mostly do their job, but with many documented flaws. A successor to transaction names is being designed in #227
Types of hooks
Hooks get executed at specific points in Dredd’s execution life cycle. Available types of hooks are:
beforeAll
called with all HTTP transactions before the whole test runbeforeEach
called before each HTTP transactionbefore
called before a single HTTP transactionbeforeEachValidation
called before each HTTP transaction is validatedbeforeValidation
called before a single HTTP transaction is validatedafter
called after a single HTTP transactionafterEach
called after each HTTP transactionafterAll
called with all HTTP transactions after the whole test run
Hooks inside Docker
As mentioned in Supported languages, running hooks written in languages other than JavaScript requires a dedicated hooks handler. Hooks handler is a separate process, which communicates with Dredd over a TCP socket.
If you’re running Dredd inside Docker, you may want to use a separate container for the hooks handler and then run all your containers together as described in the Docker Compose section.
However, hooks were not originally designed with this scenario in mind. Dredd gets a name of (or path to) the hooks handler in --language
and then starts it as a child process. To work around this, fool Dredd with a dummy script and set --hooks-worker-handler-host
together with --hooks-worker-handler-port
to point Dredd’s TCP communication to the other container.
Note
The issue described above is tracked in #755.