gabbi promises relief by providing a language-agnostic, nicely readable and lightweight format for testing - and thus describing - HTTP APIs. Originally created as part of an effort to improve OpenStack’s APIs, it has since grown into a useful tool for anyone working with HTTP.
gabbi tests are simply YAML files that describe HTTP requests and responses. We’ll be using the wonderful httpbin for illustration purposes:
tests:
- name: supports unencrypted connections
method: GET
url: http://httpbin.org/
status: 200
- name: supports SSL encryption
method: GET
url: https://httpbin.org/
status: 200
method
and status
are optional, but it’s nice to be explicit. In fact,
gabbi itself states that the name is «derived from 'gabby’: excessively
talkative» - i.e. gabbi prefers direct visibility and readability.
Of course we need to install gabbi in order to run these tests:
$ pip install gabbi
Using a virtual environment can help avoid sudo
privileges or polluting our global packages directory.
Once that’s done, we can feed our YAML tests to gabbi:
$ gabbi-run < http.yaml
... E supports unencrypted connections
... E supports SSL encryption
ERROR: supports unencrypted connections
[Errno 61] Connection refused
ERROR: supports SSL encryption
[Errno 61] Connection refused
----------------------------------------------------------------------
Ran 2 tests in 0.015s
FAILED (errors=2)
Oops - I wasn’t connected to the internet. Of course we’d typically test a local development server instead, but that’s not important right now. Let’s try this again:
$ gabbi-run < http.yaml
... ✓ supports unencrypted connections
... ✓ supports SSL encryption
----------------------------------------------------------------------
Ran 2 tests in 0.681s
We don’t want to hard-code the target host, so let’s use paths instead of fully qualified URLs:
tests:
- name: reports available HTTP methods
method: OPTIONS
url: /
response_headers:
allow: HEAD, OPTIONS, GET
- name: front page returns HTML
method: GET
url: /
status: 200
response_headers:
content-type: text/html; charset=utf-8
(Note that I added some vertical whitespace to separate request and response - that’s still valid YAML, of course.)
$ gabbi-run httpbin.org < http.yaml
... ✓ reports available HTTP methods
... ✓ front page returns HTML
----------------------------------------------------------------------
Ran 2 tests in 0.457s
That all seems pretty straightforward so far. Let’s ensure that the Allow
header did in fact tell us the truth:
tests:
- name: submitting data is not allowed
method: POST
url: /
request_headers:
content-type: text/plain
data: lorem ipsum
status: 405
$ gabbi-run httpbin.org < http.yaml
... ✓ submitting data is not allowed
As predicted, httpbin responded with 405 Method Not Allowed - clearly we’ll need to send our precious data to a different route:
tests:
- name: form submission
method: POST
url: /post
request_headers:
content-type: application/x-www-form-urlencoded
data: title=hello%20world&comment=lorem%20ipsum%21
status: 200
$ gabbi-run httpbin.org < http.yaml
... ✓ form submission
Ah, there we go. Note that it’s our responsibility to encode the data there - that’s a little easier if we use JSON:
tests:
- name: submitting JSON
method: POST
url: /post
request_headers:
content-type: application/json
data:
title: hello world
comment: lorem ipsum dolor sit amet
$ gabbi-run httpbin.org < http.yaml
... ✓ submitting JSON
In fact, httpbin responds with JSON, returning in the response whatever data we submit in our request. We can use JSONPath to query that response data:
tests:
- name: parsing JSON
method: POST
url: /post
request_headers:
accept: application/json
content-type: application/json
data:
title: hello world
comments: lorem ipsum dolor sit amet
status: 200
response_headers:
content-type: application/json
response_json_paths:
$.json.title: hello world
$.json.comments: lorem ipsum dolor sit amet
$ gabbi-run httpbin.org < http.yaml
... ✓ parsing JSON
It’s also possible to use data from the preceding response in a new request:
tests:
- name: "consecutive redirects - step #1"
method: GET
url: /relative-redirect/2
status: 302
response_headers:
location: /relative-redirect/1
- name: "consecutive redirects - step #2"
method: GET
url: $LOCATION
status: 302
response_headers:
location: /get
$ gabbi-run httpbin.org < http.yaml
... ✓ consecutive redirects - step #1
... ✓ consecutive redirects - step #2
Here we’re just using the Location
header, but this technique also enables us
to go full HATEOAS and test hypermedia
APIs:
tests:
- name: set up a hypermedia response
method: PUT
url: /put
request_headers:
accept: application/json
content-type: application/json
data:
title: hello world
links:
next: http://httpbin.org/cookies/set?foo=bar
- name: follow "next" link from preceding response
method: GET
url: $RESPONSE['$.json.links.next']
status: 302
response_headers:
set-cookie: foo=bar; Path=/
$ gabbi-run httpbin.org < http.yaml
... ✓ set up a hypermedia response
... ✓ follow "next" link from preceding response
Obviously coercing httpbin into providing hypermedia responses is a bit of a hack, so we’ll stop here. Hopefully this is sufficient to get started - in a future post, we might expand on this with a more realistic example. We might also delve into gabbi’s extensibility, e.g. creating a custom response handler to query HTML responses with CSS selectors.
Feel free to join the IRC channel #gabbi on FreeNode or leave feedback via Twitter.