简体   繁体   English

如何在 Rails 4 中测试控制器关注点

[英]How to test a Controller Concern in Rails 4

What is the best way to handle testing of concerns when used in Rails 4 controllers?在 Rails 4 控制器中使用时处理关注点测试的最佳方法是什么? Say I have a trivial concern Citations .说我有一个小问题Citations

module Citations
    extend ActiveSupport::Concern
    def citations ; end
end

The expected behavior under test is that any controller which includes this concern would get this citations endpoint.测试中的预期行为是任何包含此问题的控制器都将获得此citations端点。

class ConversationController < ActionController::Base
    include Citations
end

Simple.简单的。

ConversationController.new.respond_to? :yelling #=> true

But what is the right way to test this concern in isolation?但是,孤立地测试这种担忧的正确方法是什么?

class CitationConcernController < ActionController::Base
    include Citations
end

describe CitationConcernController, type: :controller do
    it 'should add the citations endpoint' do
        get :citations
        expect(response).to be_successful
    end
end

Unfortunately, this fails.不幸的是,这失败了。

CitationConcernController
  should add the citations endpoint (FAILED - 1)

Failures:

  1) CitationConcernController should add the citations endpoint
     Failure/Error: get :citations
     ActionController::UrlGenerationError:
       No route matches {:controller=>"citation_concern", :action=>"citations"}
     # ./controller_concern_spec.rb:14:in `block (2 levels) in <top (required)>'

This is a contrived example.这是一个人为的例子。 In my app, I get a different error.在我的应用程序中,我收到了不同的错误。

RuntimeError:
  @routes is nil: make sure you set it in your test's setup method.

You will find many advice telling you to use shared examples and run them in the scope of your included controllers.您会发现许多建议告诉您使用共享示例并在您包含的控制器范围内运行它们。

I personally find it over-killing and prefer to perform unit testing in isolation, then use integration testing to confirm the behavior of my controllers.我个人觉得这太过分了,更喜欢单独执行单元测试,然后使用集成测试来确认我的控制器的行为。

Method 1: without routing or response testing方法 1:不进行路由或响应测试

Create a fake controller and test its methods:创建一个假控制器并测试它的方法:

describe MyControllerConcern do
  before do
    class FakesController < ApplicationController
      include MyControllerConcern
    end
  end

  after do
    Object.send :remove_const, :FakesController 
  end

  let(:object) { FakesController.new }

  it 'my_method_to_test' do
    expect(object).to eq('expected result')
  end

end

Method 2: testing response方法二:测试响应

When your concern contains routing or you need to test for response, rendering etc... you need to run your test with an anonymous controller.当您关注的问题包含路由或您需要测试响应、渲染等时……您需要使用匿名控制器运行您的测试。 This allow you to gain access to all controller-related rspec methods and helpers:这允许您访问所有与控制器相关的 rspec 方法和帮助程序:

describe MyControllerConcern, type: :controller do
  controller(ApplicationController) do
    include MyControllerConcern

    def fake_action; redirect_to '/an_url'; end
  end

  before do
    routes.draw {
      get 'fake_action' => 'anonymous#fake_action'
    }
  end
    
  describe 'my_method_to_test' do
    before do
      get :fake_action 
    end

    it do
      expect(response).to redirect_to('/an_url') 
    end
  end
end

As you can see, we define the anonymous controller with controller(ApplicationController) .如您所见,我们使用controller(ApplicationController)定义了匿名控制器。 If your test concerne another class than ApplicationController , you will need to adapt this.如果您的测试涉及除ApplicationController之外的另一个类,您将需要对此进行调整。

Also for this to work properly you must configure the following in your spec_helper.rb file:同样为了使其正常工作,您必须在spec_helper.rb文件中配置以下内容:

config.infer_base_class_for_anonymous_controllers = true

Note: keep testing that your concern is included注意:继续测试您的担忧是否包括在内

It is also important to test that your concern class is included in your target classes, one line suffice:测试您的关注类是否包含在您的目标类中也很重要,一行就足够了:

describe SomeTargetedController do
  it 'includes MyControllerConcern' do
    expect(SomeTargetedController.ancestors.include? MyControllerConcern).to be(true) 
  end
end

Simplifying on method 2 from the most voted answer.从投票最多的答案中简化方法 2。

I prefer the anonymous controller supported in rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller我更喜欢 rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller 中支持的anonymous controller

You will do:你会去做的:

describe ApplicationController, type: :controller do
  controller do
    include MyControllerConcern

    def index; end
  end

  describe 'GET index' do
    it 'will work' do
      get :index
    end
  end
end

Note that you need to describe the ApplicationController and set the type in case this does not happen by default.请注意,您需要描述ApplicationController并设置类型,以防默认情况下不会发生这种情况。

My answer may look bit more complicated than these by @Benj and @Calin, but it has its advantages.我的答案可能看起来比@Benj 和 @Calin 的答案复杂一些,但它有其优点。

describe Concerns::MyConcern, type: :controller do

  described_class.tap do |mod|
    controller(ActionController::Base) { include mod }
  end

  # your tests go here
end

First of all, I recommend the use of anonymous controller which is a subclass of ActionController::Base , not ApplicationController neither any other base controller defined in your application.首先,我建议使用匿名控制器,它是ActionController::Base的子类,而不是ApplicationController定义的任何其他基本控制器。 This way you're able to test the concern in isolation from any of your controllers.通过这种方式,您可以独立于任何控制器来测试关注点。 If you expect some methods to be defined in a base controller, just stub them.如果您希望在基本控制器中定义某些方法,只需存根它们即可。

Furthermore, it is a good idea to avoid re-typing concern module name as it helps to avoid copy-paste errors.此外,避免重新输入关注模块名称是一个好主意,因为它有助于避免复制粘贴错误。 Unfortunately, described_class is not accessible in a block passed to controller(ActionController::Base) , so I use #tap method to create another binding which stores described_class in a local variable.不幸的是,在传递给controller(ActionController::Base)的块中无法访问described_class ,因此我使用#tap方法创建另一个绑定,将described_class存储在局部变量中。 This is especially important when working with versioned APIs.这在使用版本化 API 时尤其重要。 In such case it is quite common to copy large volume of controllers when creating a new version, and it's terribly easy to make such a subtle copy-paste mistake then.在这种情况下,在创建新版本时复制大量控制器是很常见的,然后很容易犯这种微妙的复制粘贴错误。

I am using a simpler way to test my controller concerns, not sure if this is the correct way but seemed much simpler that the above and makes sense to me, its kind of using the scope of your included controllers.我正在使用一种更简单的方法来测试我的控制器问题,不确定这是否是正确的方法,但似乎比上面更简单并且对我有意义,它使用包含的控制器的范围。 Please let me know if there are any issues with this method.如果此方法有任何问题,请告诉我。 sample controller:样品控制器:

class MyController < BaseController
  include MyConcern

  def index
    ...

    type = column_type(column_name)
    ...
  end

end结尾

my controller concern:我的控制器关注:

module MyConcern
  ...
  def column_type(name)
    return :phone if (column =~ /phone/).present?
    return :id if column == 'id' || (column =~ /_id/).present?
   :default
  end
  ...

end

spec test for concern:关注的规格测试:

require 'spec_helper'

describe SearchFilter do
  let(:ac)    { MyController.new }
  context '#column_type' do
    it 'should return :phone for phone type column' do
      expect(ac.column_type('phone')).to eq(:phone)
    end

    it 'should return :id for id column' do
      expect(ac.column_type('company_id')).to eq(:id)
    end

    it 'should return :id for id column' do
      expect(ac.column_type('id')).to eq(:id)
    end

    it 'should return :default for other types of columns' do
      expect(ac.column_type('company_name')).to eq(:default)
    end
  end
end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM