Cacomania: Test your RESTful API with Frisby

Cacomania

Test your RESTful API with Frisby

Guido Krömer - 1. March 2014 - Tags: ,

Writing a RESTful API is easy, but ensuring that small changes/fixes to the API does not break compatibility with existing clients consuming the API is not easy.

Not easy without functional tests, with the usage of a framework specified in REST API testing like Frisby even the tests are written easily. Frisby is build on top of Jasmine a node.js based BDD framework. Maybe you API runs with node or you are using it just for building your front end with Grunt for example, integrating node into your development process has already happened. If you are not planning to use node you should stop reading here and take a look at Codeception a complete testing framework which uses PHP for example.

Installation

Thanks to npm installing Frisby is piece of cake...

npm install -g frisby

Writing a simple test

As API endpoint for the tests I use the RESTful API from my last project CacoCloud. Let's begin with a simple test, which only checks the HTTP status code. The Frisby create() method defines a new test, the first parameter sets the name of the test. The get() method performs a HTTP GET request on the given URL. With expectStatus() the response code can be checked. The last method of each Frisby test is the toss() method which fires the request and performs the checks assigned to the test. The code below show the first simple status check test for the bookmark API.

var frisby = require('frisby');

frisby.create('API: List bookmarks')
    .get('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark')
    .expectStatus(200)
    .toss();

Testing the status code only, is not very significant. With expectHeaderContains() parts the response header can be verified. Since the API speaks JSON, the content-type should be 'application/json' on each request. Adding this to our simple status check makes test more complete.

frisby.create('API: List bookmarks')
    .get('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark')
    .expectStatus(200)
    .expectHeaderContains('content-type', 'application/json')
    .toss();

The example check is fine, but it does not verifies the response itself, sometimes it might be hard to check the response if you don not know the data the response contains. You could add a new bookmark to the API, but the title might be different even if the URL is the same. The database id is although sometime hard to test. Therefore, Frisby supports checking the structure of the response without checking the data itself. With the expectJSONTypes() method such a structure check can be performed, the parameter passed to this method is a JavaScript object describing the structure. The bookmark API response on a simple GET request is a list of bookmarks, each bookmark has the four fields url, name, date and id. The URL and the name are strings and others are numbers. The example below extends the API test, with a structure check of the response type of a bookmark API request.

frisby.create('API: List bookmarks')
    .get('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark')
    .expectStatus(200)
    .expectHeaderContains('content-type', 'application/json')
    .expectJSONTypes({
        status: Number,
        response: [
            {
                url: String,
                name: String,
                date: Number,
                id: Number
            }
        ]
    })
    .toss();

Running the test

Jasmine runs all test files from the given folder, which are called *_spec.js. If Jasmine is not installed globally, giving the full path from the document root lets you run Jasmine, too.

With the use of the --junitreport flag, the Frisby tests will generate JUnitReport compatible files which can be integrated into your "Continuous integration".

node_modules/jasmine-node/bin/jasmine-node spec/api/

    Finished in 1.675 seconds
41 tests, 111 assertions, 110 failures, 0 skipped

Complex testing with Frisby

Simple tests like the one described above cover's many parts of a RESTful API, but testing CRUD operations for example needs a couple of tests which has be executed in the right sequence. Since Frisby is based on Jasmine and Jasmine itself runs with Node.js which works asynchronous, the tests has to be nested to be executed in the right order.

The afterJSON() method executes the callback provided as first parameter after the toss received a response from the API, the callbacks first parameter itself contains the API response. By using the data provided by the response nested tests can be started.

The example below creates a new bookmark, after the POST has been send to server the response is used for fetching the bookmark and finally deleting it. To ensure the bookmark gets deleted after the test, which fetches the bookmark form the API by its id, the delete tests has to be fired in the GET tests afterJSON().

The example above just tests for the right response structure, the test below tests for the right data, too. The expectJSON() method can be used for testing a response since we know the url, name and id of the newly added bookmark those data can be used for verifying the response.

frisby.create('API: Add a new bookmark')
    .post('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark', {
        url: 'http://www.example.com',
        name: 'Example Com'
    }, {json: true})
    .expectHeaderContains('content-type', 'application/json')
    .expectStatus(201)
    .expectJSONTypes({
        status: Number,
        response: Number
    })
    .afterJSON(function (api) {
        var id = api.response;

        frisby.create('API: Get the newly added bookmark')
            .get('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark/' + id)
            .expectHeaderContains('content-type', 'application/json')
            .expectStatus(200)
            .expectJSONTypes({
                status: Number,
                response: {
                    url: String,
                    name: String,
                    date: Number,
                    id: Number
                }
            })
            .expectJSON({
                status: 200,
                response: {
                    id : id
                    url: 'http://www.example.com',
                    name: 'Example Com'
                }
            })
            .afterJSON(function (api) {
                frisby.create('API: Delete the bookmark')
                    .delete('http://TEST_USER:TEST_PASSWORD@localhost:8000/api/1/bookmark/' + id)
                    .expectHeaderContains('content-type', 'application/json')
                    .expectStatus(200)
                    .expectJSONTypes({
                        status: Number,
                        response: Number
                    })
                    .expectJSON({
                        status: 200,
                        response: id
                    })
                    .toss();
            })
            .toss();
    })
    .toss();

Test you PHP based API with the build-in web server

Is the RESTful API PHP based? If it is, the build-in web server of PHP 5.4 could be used to provide a HTTP server for running the test without the complex configuration of a Apache or Nginx server. Even if this single threaded server does not fit any production environment it is great for testing

The bash script below start the build-in web server of PHP 5.4 on the given port, runs the tests and stops the server subsequently.

#!/bin/bash

function start_php_build_in_http_server () {
  echo "Starting PHP build-in web server"
  php -S 127.0.0.1:8000 -t public &
  PHP_SERVER_PID="$!"
}

function stop_php_build_in_http_server () {
  echo "Stopping PHP build-in web server"
  kill "$PHP_SERVER_PID"
}

function run_tests () {
  echo "Running the tests now..."
  node_modules/jasmine-node/bin/jasmine-node --junitreport spec/api/
}

start_php_build_in_http_server
run_tests
stop_php_build_in_http_server

Conclusion

Writing functional tests with Frisby helps you don not breaking the API when fixing a bug or adding new features, but it is although quite easy and fast to write tests for your API.

I hope you enjoyed my petty tutorial, if you want to see some more API testing examples by latest project CacoCloud has a fully tested API using Frisby. Feel free to leave a comment if you liked or disliked my posting.