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.