Testing APIs

I recently had the opportunity to test an API and wanted to validate the results along with the structure of the response. When looking around on the internet for inspiration on how to solve this I was intrigued by the wonderful DSL created by Frisby.

My goal was to not only test the security and correctness internally but also the “user interface” of response codes and structure. I think the later is extremely important due to the nature of objects being easily changeable without any restrictions by the framework.

Frisby’s DSL solves this by having chainable method calls to validate the http status, json schema and json body. All three of which are the “user interface” our customers would be looking at on a daily basis.

The following class provides a similar DSL for validating requests sent with Rack::Test. The JSON schema is validated with the json-schema gem.

class ValidateResponse
  include RSpec::Matchers

  attr_reader :response

  delegate :status_code, to: Rack::Utils

  def initialize(response)
    @response = response
  end

  def self.with(response)
    new(response)
  end

  def expect_status(status)
    expect(status_code(response.status)).to eq(status_code(status))
    self
  end

  def expect_schema(schema)
    errors = JSON::Validator.fully_validate(schema, response.body)

    if errors.any?
      raise RSpec::Expectations::ExpectationNotMetError.new(errors)
    end

    self
  end

  def expect_body(body)
    expect(JSON.parse(response.body)).to eq(body)
    self
  end

end

Using this class is very simple and combined with the last_response method provided by Rack::Test makes this a breeze.

ValidateResponse.with(last_response)
  .expect_status(:ok)
  .expect_schema("./features/api/schemas/projects/index.json")
  .expect_body([
    {
      "id" => 1,
      "title" => "project-title",
      "created_at" => "2017-01-01T00:00:00.000Z",
      "updated_at" => "2017-01-01T00:00:00.000Z"
    }
  ])

With the ValidateResponse class in place validating the important public facing parts of our API can be done quickly with a pleasant DSL.