When you decide you’re going to integrate a web-based API like Orchestrate into your application, writing tests can seem daunting, annoying or even impossible. But fear not! I’m going to share a really easy way to write API tests. By the end of this post, you should have a clear picture of how to test your application when using APIs like Orchestrate.

Hat tip to Myron Marston for a great addition to the Ruby (and programming) community, called VCR. Rather than trying to describe what VCR does in my own words, I’ve lifted what Myron says from the VCR’s README:

Record your test suite’s HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.

The moniker “VCR” is a fantastic metaphor for what the library actually does. It “records” by serializing (generally in YAML) the request and response of the HTTP interactions as the test suite exercises the application code. These recordings are known as “cassettes” and are usually called fixtures in your test suite. The fixtures are then used to mock the request and response of the HTTP or network libraries without actually sending anything over the wire.

The quick example below is in Python, but you can do this in many other languages:

Now for the feature presentation—Below, I will demonstrate the use of the Python port of VCR, VCR.py. For this example, I’ve chosen to write tests for a community-contributed Orchestrate client library written by Jeremy Brown called orchestrate-py. The client library is fairly new, and as Jeremy pointed out:

"This is early in it’s development and subject to change. Collaborators, suggestions and testers welcome.”

I’m also going to use a testing suite called py.test.

Step 1 – Install VCR.py or Add It to Your Dependencies.

We’re using Python’s pip to install and freeze the dependencies from the root directory of our repository.

pip install vcrpy
pip freeze > requirements.txt

Step 2 – Import and Configure VCR.py in a test_orchestrate.py

import json
import pytest
import vcr
from orchestrate import client

client.set_auth("YOUR-API-KEY")

my_vcr = vcr.VCR(
 serializer = 'json',
 cassette_library_dir = 'fixtures/vcr_cassettes',
 record_mode = 'once',
 match_on = ['url', 'method', 'headers'],
)

Keeping the API key in the code is OK in this instance because, with Orchestrate and many other services, it’s easy to destroy an API key and regenerate a new one. We’ll want to do that at the end once we publish our commits; then only the old, inactive key will be in the fixtures and tests.

Step 3 – Write Your Test

Here we’re just simply testing the ability to put a key and value into a collection in Orchestrate.

def test_put_key_value():
 user = {
 'age': 2,
 'location': 'Portland',
 'name': 'Dexter Heitzenroder'
 }
 r1 = client.put_key_value('users', 'dexter', json.dumps(user))
 r2 = client.get_key_value('users', 'dexter')
 assert r1.status_code == 201
 assert r1.headers['content-type'] == 'application/json'
 assert r2.json() == user

At this point, we’ll want to run our test suite and make sure the tests pass.

Step 4 – Decorate Your Test with VCR

Now that we have a working test, we simply decorate it with VCR’s fixture (aka “cassette”) location

@vcr.use_cassette('fixtures/vcr_cassettes/put_key_value.yaml')
def test_put_key_value():
 user = {
 'age': 2,
 'location': 'Portland',
 'name': 'Dexter Heitzenroder'
 }
 r1 = client.put_key_value('users', 'dexter', json.dumps(user))
 r2 = client.get_key_value('users', 'dexter')
 assert r1.status_code == 201
 assert r1.headers['content-type'] == 'application/json'
 assert r2.json() == user

Note the use of:

@vcr.use_cassette('fixtures/vcr_cassettes/put_key_value.yaml')

When I decorated the function, I didn't create the directory structure or the file name. VCR.py will auto-magically create this for us the next time we run the test suite. Based on our configuration of VCR.py in step 1, we are going to use the record mode “once”. This means we’ll let the HTTP library make a call once and write the output to our cassette file. There are other modes, discussed in VCR.py’s documentation.

Step 5 – Run and Record Your Test

This is straightforward enough. In our case, since we’re using py.test, all we do is:

py.test test_orchestrate.py

Once we’ve done this, we can see that the fixtures (aka “cassettes”) have been recorded and placed in: ./fixtures/vcr_cassettes/put_key_value.yaml. Feel free to explore it, since it is written in YAML it is pretty straightforward.

- request: !!python/object:vcr.request.Request
 body: '{"age": 2, "location": "Portland", "name": "Dexter Heitzenroder"}'
 headers: !!python/object/apply:__builtin__.frozenset
 - - !!python/tuple [Content-Length, '65']
 - !!python/tuple [Authorization, !!python/unicode Basic MWFmNTc1NzYtZmI0OC00ZDcyLWE3ZDMtZGU0NjljOTMxN2E1Og==]
 - !!python/tuple [User-Agent, python-requests/2.2.1 CPython/2.7.6 Darwin/13.1.0]
 - !!python/tuple [Accept, '*/*']
 - !!python/tuple [Content-Type, application/json]
 - !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
 host: api.orchestrate.io
 method: PUT
 path: /v0/users/dexter
 port: 443
 protocol: https
 response:
 body: {string: !!python/unicode ''}
 headers: [!!python/unicode "Content-Type: application/json\r\n", !!python/unicode "Date:
 Thu, 13 Mar 2014 03:03:18 GMT\r\n", !!python/unicode "Location: /v0/users/dexter/refs/44847c00aaaa3311\r\n",
 !!python/unicode "X-ORCHESTRATE-REQ-ID: 074b5ee0-aa5c-11e3-a76e-12313d2f50f8\r\n",
 !!python/unicode "transfer-encoding: chunked\r\n", !!python/unicode "Connection:
 keep-alive\r\n"]
 status: {code: 201, message: Created}
- request: !!python/object:vcr.request.Request
 body: null
 headers: !!python/object/apply:__builtin__.frozenset
 - - !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
 - !!python/tuple [User-Agent, python-requests/2.2.1 CPython/2.7.6 Darwin/13.1.0]
 - !!python/tuple [Accept, '*/*']
 - !!python/tuple [Authorization, !!python/unicode Basic MWFmNTc1NzYtZmI0OC00ZDcyLWE3ZDMtZGU0NjljOTMxN2E1Og==]
 - !!python/tuple [Content-Type, application/json]
 host: api.orchestrate.io
 method: GET
 path: /v0/users/dexter
 port: 443
 protocol: https
 response:
 body:
 string: !!binary |
 H4sIAAAAAAAAAKtWSkxPVbIy0lHKyU9OLMnMz1OyUgrILyrJScxLUdJRykvMBUoruaRWlKQWKXik
 ZpZUpeYV5aekFinVAgCn8IGvPAAAAA==
 headers: [!!python/unicode "Content-Encoding: gzip\r\n", !!python/unicode "Content-Location:
 /v0/users/dexter/refs/44847c00aaaa3311\r\n", !!python/unicode "Content-Type:
 application/json\r\n", !!python/unicode "Date: Thu, 13 Mar 2014 03:03:19 GMT\r\n",
 !!python/unicode "ETag: \"44847c00aaaa3311-gzip\"\r\n", !!python/unicode "Vary:
 Accept-Encoding, User-Agent\r\n", !!python/unicode "X-ORCHESTRATE-REQ-ID:
 07907e30-aa5c-11e3-94ea-12313d2f9238\r\n", !!python/unicode "transfer-encoding:
 chunked\r\n", !!python/unicode "Connection: keep-alive\r\n"]
 status: {code: 200, message: OK}

Note, the fixture has recorded the Basic Authentication headers. Since this is hard-coded into our fixture and we will check this into a repository, we will want to destroy our API key that we used for testing.

Step 6 – Run Your Test Again and Enjoy

Finally, if you run your test again, you’ll notice a marked performance gain in testing. This is because there isn’t any network traffic—VCR.py is handling those requests for us and using the fixtures we have defined.

Have fun building great things with Orchestrate. By the way, we're live now in the CenturyLink Cloud. Sign up today for Orchestrate at orchestrate.io.