How do I test my JSON API with Sinatra + rspec

api, rspec, sinatra

Solution

The code you've used is fine, although I would structure it slightly differently as I don't like to use `it` blocks the way you normally see them, I think it encourages testing of more than one aspect of a system at a time:

let(:body) { { :key => "abcdef" }.to_json }
before do
  post '/channel/create', body, {'CONTENT_TYPE' => 'application/json'}
end
subject { last_response }
it { should be_ok }

I've used let because it's better than an instance variable in a `before` block (kudos to you for not doing that). The `post` is in a `before` block because it's not really part of the spec, but a side effect that occurs prior to what you're speccing. The `subject` is the response and that makes the `it` a simple call.

Because checking the response is ok is needed so often I put it in a shared example:

shared_examples_for "Any route" do
  subject { last_response }
  it { should be_ok }
end

and then call it as such:

describe "Creating a new channel" do
  let(:body) { { :key => "abcdef" }.to_json }
  before do
    post '/channel/create', body, {'CONTENT_TYPE' => 'application/json'}
  end
  it_should_behave_like "Any route"
  # now spec some other, more complicated stuff…
  subject { JSON.parse(last_response.body) }
  it { should == "" }

and because the content type changes so often, I put that in a helper:

  module Helpers

    def env( *methods )
      methods.each_with_object({}) do |meth, obj|
        obj.merge! __send__(meth)
      end
    end

    def accepts_html
      {"HTTP_ACCEPT" => "text/html" }
    end

    def accepts_json 
      {"HTTP_ACCEPT" => "application/json" }
    end

    def via_xhr      
      {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
    end

It's easy to add this in where it's needed by including it via the RSpec config:

RSpec.configure do |config|
  config.include Helpers, :type => :request

then:

describe "Creating a new channel", :type => :request do
  let(:body) { { :key => "abcdef" }.to_json }
  before do
    post '/channel/create', body, env(:accepts_json)
  end

Having said all that, personally, I wouldn't post using JSON. HTTP POST is simple to handle, and every form and javascript library does it easily and well. Respond with JSON by all means, but don't post JSON, HTTP is a lot easier.

Edit: after writing out the `Helpers` bit above I realised it would be more helpful as a gem.

Problem

I have a post method that accepts JSON: ``` post '/channel/create' do content_type :json @data = JSON.parse(env['rack.input'].gets) if @data.nil? or !@data.has_key?('api_key') status 400 body({ :error => "JSON corrupt" }.to_json) else status 200 body({ :error => "Channel created" }.to_json) end ``` As a newbie to rspec I am bewildered trying to figure out how to write a test against that POST with an acceptable JSON payload. The closest I got to is this which is woefully inaccurate but I don't seem to be asking the Google god the right questions to help me out here. ``` it "accepts create channel" do h = {'Content-Type' => 'application/json'} body = { :key => "abcdef" }.to_json post '/channel/create', body, h last_response.should be_ok end ``` Any best practice guidance for testing APIs in Sinatra will be most appreciated also.

Original source

Related problems