【问题标题】:Using Rspec, how do I test the JSON format of my controller in Rails 3.0.11?使用 Rspec,如何在 Rails 3.0.11 中测试我的控制器的 JSON 格式?
【发布时间】:2012-03-23 12:31:03
【问题描述】:

我搜索了网络,但是,唉,我似乎无法让 Rspec 正确发送内容类型,因此我可以测试我的 JSON API。我将 RABL gem 用于模板、Rails 3.0.11 和 Ruby 1.9.2-p180。

我的 curl 输出,效果很好(应该是 401,我知道):

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

来自我的一个测试用例的示例:

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

但这些测试从未通过,我总是被重定向并且我的内容类型总是text/html,不管我在之前的块中如何指定类型:

# 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

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

如您所见,我得到了 HTML 格式的 302 重定向,即使我尝试指定 'application/json'。

这是我的application_controller.rb,带有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

还有applications_controller.rb,特别是我正在尝试测试的“创建”操作。目前它相当难看,因为我正在使用state_machine 并覆盖删除方法。

  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

我在这里查看了源代码:https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb,它似乎应该正确响应,以及一些关于堆栈溢出的问题似乎有类似的问题和可能的解决方案,但没有一个对我有用.

我做错了什么?

【问题讨论】:

    标签: ruby-on-rails json api header rspec


    【解决方案1】:

    我意识到设置:format =&gt; :json 是一种解决方案(如上所述)。但是,我想测试我的 API 的客户端将使用的相同条件。我的客户不会设置:format 参数,而是设置Accept HTTP 标头。如果您对此解决方案感兴趣,这是我使用的:

    # 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
    

    【讨论】:

    • 添加request.accept = "application/json" 解决了我的问题。这似乎是最简单的解决方案。谢谢!
    • Rails 4 和 Rspec 3.2.1 这解决了同样的问题
    • Rails4 和 Rspec3.3 也是如此
    【解决方案2】:

    尝试在请求的 params 哈希中移动 :format 键,如下所示:

    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
    

    告诉我进展如何!这就是我设置测试的方式,它们工作得很好!

    【讨论】:

    • 感谢亚瑟回复我。我做了你要求的改变,但我仍然得到相同的结果,测试被重定向到 HTML 1) ApplicationsController JSON creating a new application when not authorized should not allow creation of an application Failure/Error: JSON.parse(response.body)["status"] == "errors" JSON::ParserError: 756: unexpected token at '&lt;html&gt;&lt;body&gt;You are being &lt;a href="http://test.host/"&gt;redirected&lt;/a&gt;.&lt;/body&gt;&lt;/html&gt;' # ./spec/controllers/applications_controller_spec.rb:25:in block (5 levels) in &lt;top (required)&gt;'
    • 刚刚查看了您的控制器代码!我改变了一下测试!试试看!
    • 不幸的是,根据api.rubyonrails.org/classes/ActionController/TestCase/…,这也行不通。如果不指定 Content-TypeAccept 标头,Rails 将默认为 text/html。类似于此 curl 语句:curl -i -X POST -d '{ :application =&gt; {:name =&gt; "foo", :description =&gt; "bar"}}' http://localhost:3000/applications
    • @gaahrdner 尝试将格式设置为参数!这就是我的 API rspec 测试!它正在工作!我没有使用 Content-Type 或 Accept
    • 啊哈!成功!这完全有效,它也适用于before 块。非常感谢亚瑟!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-11
    相关资源
    最近更新 更多