Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test my JSON API with Sinatra + rspec

Tags:

rspec

sinatra

api

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 [email protected]_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.

like image 290
Etienne Avatar asked Feb 21 '13 19:02

Etienne


1 Answers

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.

like image 90
ian Avatar answered Nov 15 '22 20:11

ian