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.