59 votes

À l'aide de Rspec, comment puis-je tester le format JSON de mon contrôleur dans les Rails 3.0.11?

J'ai écumé le web, mais, hélas, je ne peux pas sembler obtenir Rspec pour envoyer correctement le type de contenu de sorte que je puisse tester mon API JSON. Je suis à l'aide de la RABL gem pour les modèles de Rails 3.0.11, et Ruby 1.9.2-p180.

Mon curl sortie, qui fonctionne très bien (ce qui 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."}

L'échantillon de l'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 passent jamais bien, j'ai toujours été redirigé et mon type de contenu est toujours text/html, indépendamment de la façon dont je semble spécifier le type dans mon avant de bloc:

# 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 rspec de sortie:

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 je suis de la redirection 302 pour le format HTML, même si je suis d'essayer de préciser, "application/json".

Voici mon application_controller.rb, avec la rescue_from bits:

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, plus précisément de la "création" de l'action qui est ce que je suis en train de tester. C'est assez moche pour le moment car je suis en utilisant state_machine et en remplaçant la méthode delete.

  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.rbet il semble qu'il doit répondre correctement, ainsi que d'un certain nombre de questions sur stack overflow qui semblent avoir les mêmes problèmes et les solutions possibles, mais aucun travail pour moi.

Ce que je fais mal?

67voto

sorens Points 2835

Je me rends compte que le paramètre :format => :json est une solution (comme indiqué ci-dessus). Cependant, j'ai voulu tester les mêmes conditions que les clients de mon API va l'utiliser. Mes clients ne serait pas le réglage de l' :format paramètre, au lieu qu'ils seraient réglage de l' Accept - tête HTTP. 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

62voto

Arthur Neves Points 5753

essayez de déplacer le post dans le "elle" du bloc, 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
          json = { :format => 'json', :application => { :name => "foo", :description => "bar" } }
          post :create, json
          Application.count.should == 0
          response.status.should eq(403)
          JSON.parse(response.body)["status"] == "error"
          JSON.parse(response.body)["message"] =~ /authorized/
        end 


      end 

      context "authorized" do
      end 
    end
  end
end

Laissez-moi savoir comment ça se passe! c'est la façon dont j'ai mis mes tests, et ils fonctionnent bien!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X