【问题标题】:Test controllers without going through routing不经过路由测试控制器
【发布时间】:2013-03-14 16:13:10
【问题描述】:

我正在尝试单独测试我的控制器的动作链。具体来说,我想确保将我想要的行为应用于我的所有控制器的操作。例如,测试我的所有操作是否需要身份验证:

context "when not authenticated" do

  # single case
  describe "GET index" do
    it "responds with 401" do
      get :index
      response.code.should be(401)
    end
  end

  # all of them...
  described_class.action_methods.each do |action|
    ['get', 'put', 'post', 'delete', 'patch'].each do |verb|
      describe "#{verb.upcase} #{action}" do
        it "responds with 401" do
          send verb, action
          response.code.should == "401"
        end
      end
    end   
  end

end

我希望这会起作用,但事实并非如此。我得到了一些ActionController::RoutingErrors。这是因为我的一些路线需要参数,在某些情况下我没有提供它们(比如当我打电话给post :create 时)。我明白了。但我不明白的是:为什么这很重要!?

对于这些测试,路由是一个单独的问题。我关心的是我的行动链,而不是我的请求(这就是我有 routing specsrequest specs 的目的)。在这个级别上,我不需要担心我的路线限制。

所以我的问题是:有没有办法只测试动作链而不模拟请​​求?

编辑:一些研究

看起来TestCase#process 中正在执行路由。有必要吗?

【问题讨论】:

    标签: ruby-on-rails testing rspec controllers


    【解决方案1】:

    一种解决方法是放松路由引擎的约束。这不会绕过路由,但确实可以更轻松地进行测试。

    在您的规范中添加如下内容:

    before(:all) do
      Rails.application.routes.draw { match ':controller(/:action)' }
    end
    after(:all) do
      Rails.application.reload_routes!
    end
    

    虽然严格来说不是问题的答案,但它可能是一个足够好的解决方法。

    【讨论】:

    • 这对我有用。我用match 设置了一条catch all 路由,所以控制器没有直接被路由到。
    【解决方案2】:

    我认为路由不是控制器规范的一个单独问题。原因之一是根据传入 url 的值将值添加到 params 哈希中,而控制器中的代码可能取决于这些值。

    无论如何,我假设您在ApplicationController 中定义了某种授权方法。单独测试每个控制器似乎有点多余。这是我的做法:

    require "spec_helper"
    
    describe ApplicationController do
      describe "require_current_user" do
        ACTIONS_AND_VERBS = [
          [:index,   :get],
          [:show,    :get],
          [:new,     :get],
          [:create,  :post],
          [:edit,    :get],
          [:update,  :put],
          [:destroy, :delete],
        ]
    
        controller do      
          ACTIONS_AND_VERBS.each do |action, _|
            define_method(action) do
            end
          end
        end
    
        ACTIONS_AND_VERBS.each do |action, verb|
          describe "#{verb.to_s.upcase} '#{action}'" do
            it "should be successful" do
              send(verb, action, id: -1)
              response.code.should eq("401")
            end
          end
        end
      end
    end
    

    而在我的ApplicationController 中,我会有类似...

    class ApplicationController < ActionController::Base
      protect_from_forgery
    
      before_filter :require_current_user
    
      def require_current_user
        head :unauthorized
      end
    end
    

    编辑:如果我理解正确,我们真正测试的是您的require_current_user 或您想要进行的任何等效授权过程是否按预期工作。在这种情况下,我们可以只测试一个动作,并相信before_filter 可以正常工作。

    require "spec_helper"
    
    describe ApplicationController do
      describe "require_current_user" do
        controller do
          def index
          end
        end
    
        it 'should head unauthorized for unauthorized users' do
          get :index
          response.code.should eq("401")
        end
      end
    end
    

    【讨论】:

    • 回复:参数。是的,但 params 散列中的值不需要来自路由。例如,它们可能来自表单。如果我的控制器依赖于参数,那么我应该直接设置这些参数,而不是通过一些路由魔法间接设置。
    • 回复:冗余。我闻到你的方法有问题。一方面,您的控制器共享一个公共基础这一事实是一个实现细节。尽管我们所有控制器的目标结果是相同的,但我们到达那里的方式可能对每个控制器都不同。其次,没有什么可以阻止子类覆盖其超类,因此测试超类是不够的。最后,子类可以定义动作——这意味着超类测试需要知道所有这些。漏水。我认为实际的控制器实例是我们这里的原子单元。
    • 你的方法是干的。当谈到这些身份验证策略时,我只是(也是?)偏执狂。
    • @thedeeno 但是如果你的控制器使用像request.path_parametersrequest.query_parametersrequest.request_parameters这样的调用呢?如果您仍然想绕过路由器,则必须以另一种方式表达哪些参数来自哪里。
    • @thedeeno 关于我建议的方法:我想我对您的一些不满感到有些困惑。我不确定你所说的“我们到达那里的方式可能对他们每个人都不同”是什么意思。我还没有遇到过我的任何控制器都没有直接或间接从ActionController::Base 继承的情况,那么测试从它继承的匿名类有那么糟糕吗?我确实理解您关于测试其他操作的观点,但无论如何您确实只需要测试一个。规范应该测试您的 require_current_user 等效项的功能,而不是 rails API。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-05
    • 2013-06-17
    • 2014-10-04
    • 1970-01-01
    相关资源
    最近更新 更多