web-dev-qa-db-fra.com

En utilisant Rspec, comment puis-je tester le format JSON de mon contrôleur dans Rails 3.0.11?

J'ai parcouru le Web, mais, hélas, je n'arrive pas à faire en sorte que Rspec envoie correctement le type de contenu afin que je puisse tester mon API JSON. J'utilise la gemme RABL pour les modèles, Rails 3.0.11 et Ruby 1.9.2-p180.

Ma sortie curl, qui fonctionne bien (devrait être un 401, je sais):

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

Échantillon d'un de mes cas de test:

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

Ces tests ne réussissent jamais, je suis toujours redirigé et mon type de contenu est toujours text/html, quelle que soit la façon dont je semble spécifier le type dans mon bloc avant:

# 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

Voici la sortie 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)>'

Comme vous pouvez le voir, j'obtiens la redirection 302 pour le format HTML, même si j'essaie de spécifier "application/json".

Voici mon application_controller.rb, avec le 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

Et aussi applications_controller.rb, en particulier l'action "créer" qui est ce que j'essaie de tester. C'est assez moche pour le moment car j'utilise state_machine et en remplaçant la méthode de suppression.

  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

J'ai regardé le code source ici: https://github.com/Rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb , et il semble qu'il devrait répondre correctement, ainsi qu'un certain nombre de questions sur le débordement de pile qui semblent avoir des problèmes similaires et des solutions possibles, mais aucune ne fonctionne pour moi.

Qu'est-ce que je fais mal?

57
gaahrdner

Essayez de déplacer le :format clé à l'intérieur du hachage de paramètres de la demande, comme ceci:

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

Faites-moi savoir comment ça se passe! c'est la façon dont j'ai réglé mes tests, et ils fonctionnent très bien!

60
Arthur Neves

Je me rends compte que le réglage :format => :json est une solution (comme indiqué ci-dessus). Cependant, je voulais tester les mêmes conditions que les clients de mon API utiliseraient. Mes clients ne définiraient pas le :format, ils définiraient plutôt l'en-tête HTTP Accept. Si vous êtes intéressé par cette solution, voici ce que j'ai utilisé:

# 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