Using Rspec, how do I test the JSON format of my controller in Rails 3.0.11?

68,806

Solution 1

Try moving the :format key inside the params hash of the request, like this:

describe ApplicationsController do
  render_views
  disconnect_sunspot

  let(:application) { Factory.create(:application) }

  subject { application }

  context "JSON" do

    describe "creating a new application" do

      context "when not authorized" do
        it "should not allow creation of an application" do
          params = { :format => 'json', :application => { :name => "foo", :description => "bar" } }
          post :create, params 
          Expect(Application.count).to eq(0)
          expect(response.status).to eq(403)
          expect(JSON.parse(response.body)["status"]).to eq("error")
          expect(JSON.parse(response.body)["message"]).to match(/authorized/)
        end 


      end 

      context "authorized" do
      end 
    end
  end
end

Let me know how it goes! thats the way I have set my tests, and they are working just fine!

Solution 2

I realize that setting :format => :json is one solution (as noted above). However, I wanted to test the same conditions that the clients to my API would use. My clients would not be setting the :format parameter, instead they would be setting the Accept HTTP header. If you are interested in this solution, here is what I used:

# api/v1/test_controller_spec.rb
require 'spec_helper.rb'
describe Api::V1::TestController do
  render_views
  context "when request sets accept => application/json" do
    it "should return successful response" do
      request.accept = "application/json"
      get :test
      response.should be_success
    end
  end
end
Share:
68,806
gaahrdner
Author by

gaahrdner

Updated on July 08, 2022

Comments

  • gaahrdner
    gaahrdner almost 2 years

    I've scoured the web, but, alas, I just can't seem to get Rspec to correctly send content-type so I can test my JSON API. I'm using the RABL gem for templates, Rails 3.0.11, and Ruby 1.9.2-p180.

    My curl output, which works fine (should be a 401, I know):

    mrsnuggles:tmp gaahrdner$ curl -i -H "Accept: application/json" -X POST -d @bleh http://localhost:3000/applications
    HTTP/1.1 403 Forbidden 
    Content-Type: application/json; charset=utf-8
    Cache-Control: no-cache
    X-Ua-Compatible: IE=Edge
    X-Runtime: 0.561638
    Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18)
    Date: Tue, 06 Mar 2012 01:10:51 GMT
    Content-Length: 74
    Connection: Keep-Alive
    Set-Cookie: _session_id=8e8b73b5a6e5c95447aab13dafd59993; path=/; HttpOnly
    
    {"status":"error","message":"You are not authorized to access this page."}
    

    Sample from one of my test cases:

    describe ApplicationsController do
      render_views
      disconnect_sunspot
    
      let(:application) { Factory.create(:application) }
    
      subject { application }
    
      context "JSON" do
    
        describe "creating a new application" do
    
          context "when not authorized" do
            before do
              json = { :application => { :name => "foo", :description => "bar" } }
              request.env['CONTENT_TYPE'] = 'application/json'
              request.env['RAW_POST_DATA'] = json
              post :create
            end 
    
            it "should not allow creation of an application" do
              Application.count.should == 0
            end 
    
            it "should respond with a 403" do
              response.status.should eq(403)
            end 
    
            it "should have a status and message key in the hash" do
              JSON.parse(response.body)["status"] == "error"
              JSON.parse(response.body)["message"] =~ /authorized/
            end 
          end 
    
          context "authorized" do
          end 
        end
      end
    end
    

    These tests never pass though, I always get redirected and my content-type is always text/html, regardless of how I seem to specify the type in my before block:

    # nope
    before do
      post :create, {}, { :format => :json }
    end
    
    # nada
    before do
      post :create, :format => Mime::JSON
    end
    
    # nuh uh
    before do
      request.env['ACCEPT'] = 'application/json'
      post :create, { :foo => :bar }
    end
    

    Here is the rspec output:

    Failures:
    
      1) ApplicationsController JSON creating a new application when not authorized should respond with a 403
         Failure/Error: response.status.should eq(403)
    
           expected 403
                got 302
    
           (compared using ==)
         # ./spec/controllers/applications_controller_spec.rb:31:in `block (5 levels) in <top (required)>'
    
      2) ApplicationsController JSON creating a new application when not authorized should have a status and message key in the hash
         Failure/Error: JSON.parse(response.body)["status"] == "errors"
         JSON::ParserError:
           756: unexpected token at '<html><body>You are being <a href="http://test.host/">redirected</a>.</body></html>'
         # ./spec/controllers/applications_controller_spec.rb:35:in `block (5 levels) in <top (required)>'
    

    As you can see I'm getting the 302 redirect for the HTML format, even though I'm trying to specify 'application/json'.

    Here is my application_controller.rb, with the rescue_from bit:

    class ApplicationController < ActionController::Base
    
     rescue_from ActiveRecord::RecordNotFound, :with => :not_found
    
      protect_from_forgery
      helper_method :current_user
      helper_method :remove_dns_record
    
     rescue_from CanCan::AccessDenied do |exception|
        flash[:alert] = exception.message
        respond_to do |format|
          h = { :status => "error", :message => exception.message }
          format.html { redirect_to root_url }
          format.json { render :json => h, :status => :forbidden }
          format.xml  { render :xml => h, :status => :forbidden }
        end 
      end
    
      private
    
      def not_found(exception)
        respond_to do |format|
          h = { :status => "error", :message => exception.message }
          format.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => :not_found }
          format.json { render :json => h, :status => :not_found }
          format.xml  { render :xml => h, :status => :not_found }
        end
      end
    end
    

    And also applications_controller.rb, specifically the 'create' action which is what I'm trying to test. It's fairly ugly at the moment because I'm using state_machine and overriding the delete method.

      def create
        # this needs to be cleaned up and use accepts_attributes_for
        @application = Application.new(params[:application])
        @environments = params[:application][:environment_ids]
        @application.environment_ids<<@environments unless @environments.blank?
    
        if params[:site_bindings] == "new"
          @site = Site.new(:name => params[:application][:name])
          @environments.each do |e|
            @site.siteenvs << Siteenv.new(:environment_id => e)
          end
        end
    
        if @site
          @application.sites << @site
        end
    
        if @application.save
          if @site
            @site.siteenvs.each do |se|
              appenv = @application.appenvs.select {|e| e.environment_id == se.environment_id }
              se.appenv = appenv.first
              se.save
            end
          end
          flash[:success] = "New application created."
          respond_with(@application, :location => @application)
        else
          render 'new'
        end
    
        # super stinky :(
        @application.change_servers_on_appenvs(params[:servers]) unless params[:servers].blank?
        @application.save
      end
    

    I've looked at the source code here: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb, and it seems it should respond correctly, as well as a number of questions on stack overflow that seem to have similar issues and possible solutions, but none work for me.

    What am I doing wrong?