Vraag Hoe gebruik ik Rspec, hoe test ik het JSON-formaat van mijn controller in Rails 3.0.11?


Ik heb het web doorzocht, maar helaas lijkt het erop dat Rspec niet juist inhoudstype kan verzenden, zodat ik mijn JSON API kan testen. Ik gebruik de RABL-edelsteen voor sjablonen, Rails 3.0.11 en Ruby 1.9.2-p180.

Mijn curl-uitvoer, die goed werkt (zou een 401 moeten zijn, ik weet het):

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

Voorbeeld van een van mijn testcases:

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

Deze tests gaan echter nooit voorbij, ik word altijd omgeleid en mijn inhoudstype is altijd text/html, ongeacht hoe ik het type in mijn voor blok lijkt te specificeren:

# 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 is de uitvoer van de 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)>'

Zoals u ziet, krijg ik de 302-omleiding voor het HTML-formaat, ook al probeer ik 'application / json' te specificeren.

Hier is mijn application_controller.rb, met de 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

En ook applications_controller.rb, in het bijzonder de 'create'-actie die ik probeer uit te proberen. Het is op dit moment vrij lelijk omdat ik het gebruik state_machine en overschrijven de verwijderingsmethode.

  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

Ik heb hier de broncode bekeken: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb, en het lijkt erop dat het correct zou moeten reageren, evenals een aantal vragen over stack-overflow die vergelijkbare problemen en mogelijke oplossingen lijken te hebben, maar geen enkele werkt voor mij.

Wat doe ik verkeerd?


55
2018-03-06 01:42


oorsprong


antwoorden:


Verplaats de :format sleutel in de params-hash van het verzoek, zoals dit:

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

Laat me weten hoe het gaat! dat is de manier waarop ik mijn tests heb ingesteld, en ze werken prima!


56
2018-03-06 02:27



Ik besef die instelling :format => :json is een oplossing (zoals hierboven vermeld). Ik wilde echter dezelfde voorwaarden testen die de clients voor mijn API zouden gebruiken. Mijn klanten zouden de :format parameter, in plaats daarvan zouden ze de Accept HTTP-header. Als u geïnteresseerd bent in deze oplossing, dan is dit wat ik heb gebruikt:

# 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

63
2017-10-11 02:58