[英]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.