it-swarm.com.de

Wie teste ich mit Rspec das JSON-Format meines Controllers in Rails 3.0.11?

Ich habe das Web durchsucht, aber leider kann ich Rspec nicht dazu bringen, den Inhaltstyp korrekt zu senden, damit ich meine JSON-API testen kann. Ich verwende den RABL-Edelstein für Vorlagen, Rails 3.0.11 und Ruby 1.9.2-p180.

Meine Curl-Ausgabe, die gut funktioniert (sollte eine 401 sein, ich weiß):

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."}

Probe aus einem meiner Testfälle:

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

Diese Tests bestehen jedoch nie, ich werde immer umgeleitet und mein Inhaltstyp ist immer text/html, Unabhängig davon, wie ich den Typ in meinem Before-Block angegeben habe:

# 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

Hier ist die Ausgabe von rspec:

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)>'

Wie Sie sehen, erhalte ich die 302-Umleitung für das HTML-Format, obwohl ich versuche, 'application/json' anzugeben.

Hier ist mein application_controller.rb Mit dem Bit rescue_from:

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

Und auch applications_controller.rb, Speziell die Aktion 'create', die ich zu testen versuche. Es ist im Moment ziemlich hässlich, weil ich state_machine Verwende und die Löschmethode überschreibe.

  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

Ich habe mir den Quellcode hier angesehen: https://github.com/Rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb , und es scheint so Antworten Sie richtig, sowie eine Reihe von Fragen zum Stapelüberlauf, die ähnliche Probleme und mögliche Lösungen zu haben scheinen, aber keine funktionieren für mich.

Was mache ich falsch?

57
gaahrdner

Bewegen Sie das :format Schlüssel innerhalb des params Hash der Anfrage, wie folgt:

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

Lass mich wissen, wie es geht! Auf diese Weise habe ich meine Tests festgelegt und sie funktionieren einwandfrei!

60
Arthur Neves

Mir ist klar, dass die Einstellung :format => :json Eine Lösung ist (wie oben erwähnt). Ich wollte jedoch die gleichen Bedingungen testen, die die Clients für meine API verwenden würden. Meine Clients würden nicht den Parameter :format Setzen, sondern den HTTP-Header Accept. Wenn Sie an dieser Lösung interessiert sind, habe ich Folgendes verwendet:

# 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
65
sorens