在Rails 4控制器中使用时,处理问题测试的最佳方法是什么?说我有一个微不足道的关注Citations
。
module Citations
extend ActiveSupport::Concern
def citations ; end
end
预期的测试行为是包含此问题的任何控制器都会获得此citations
端点。
class ConversationController < ActionController::Base
include Citations
end
简单。
ConversationController.new.respond_to? :yelling #=> true
但是,隔离测试这种问题的正确方法是什么?
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
不幸的是,这失败了。
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)>'
这是一个人为的例子。在我的应用程序中,我得到了一个不同的错误。
RuntimeError:
@routes is nil: make sure you set it in your test's setup method.
答案 0 :(得分:79)
您将找到许多建议,告诉您使用共享示例并在包含的控制器范围内运行它们。
我个人认为它过度杀戮并且更喜欢单独执行单元测试,然后使用集成测试来确认我的控制器的行为。
方法1:没有路由或响应测试
创建一个假控制器并测试其方法:
describe MyControllerConcern do
before do
class FakesController < ApplicationController
include MyControllerConcern
end
end
after { Object.send :remove_const, :FakesController }
let(:object) { FakesController.new }
describe 'my_method_to_test' do
it { expect(object).to eq('expected result') }
end
end
方法2:测试回复
当您的问题包含路由或您需要测试响应,呈现等...您需要使用匿名控制器运行测试。这允许您访问所有与控制器相关的rspec方法和帮助程序:
describe MyControllerConcern, type: :controller do
controller(ApplicationController) do
include MyControllerConcern
def fake_action; redirect_to '/an_url'; end
end
before { routes.draw {
get 'fake_action' => 'anonymous#fake_action'
} }
describe 'my_method_to_test' do
before { get :fake_action }
it { expect(response).to redirect_to('/an_url') }
end
end
您可以看到我们必须将匿名控制器包装在controller(ApplicationController)
中。如果您的类继承自ApplicationController
以外的其他类,则需要对其进行调整。
为了使其正常工作,您必须在 spec_helper.rb 文件中声明:
config.infer_base_class_for_anonymous_controllers = true
注意:继续测试您的顾虑是否包含在内
测试您的关注类是否包含在目标类中也很重要,一行就足够了:
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
答案 1 :(得分:24)
从最投票的答案中简化方法2。
我更喜欢rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
支持的?- samelength_of(N,[As,Bs,Cs]), my_indomain(N).
N = 0, As = [], Bs = [], Cs = [] ;
N = 1, As = [_A1], Bs = [_B1], Cs = [_C1] ;
N = 2, As = [_A1,_A2], Bs = [_B1,_B2], Cs = [_C1,_C2] ;
N = 3, As = [_A1,_A2,_A3], Bs = [_B1,_B2,_B3], Cs = [_C1,_C2,_C3] ...
你会这样做:
anonymous controller
请注意,您需要描述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
并设置类型,以防默认情况下不会发生这种情况。
答案 2 :(得分:2)
我的回答可能看起来比@Benj和@Calin更复杂,但它有其优点。
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
首先,我建议使用匿名控制器,它是ActionController::Base
的子类,而不是ApplicationController
在您的应用程序中定义的任何其他基本控制器。通过这种方式,您可以独立于任何控制器测试问题。如果您希望在基本控制器中定义某些方法,只需将它们存根。
此外,避免重新键入关注模块名称是一个好主意,因为它有助于避免复制粘贴错误。遗憾的是,described_class
在传递给controller(ActionController::Base)
的块中无法访问,因此我使用#tap
方法创建另一个将described_class
存储在局部变量中的绑定。在使用版本化API时,这一点尤其重要。在这种情况下,在创建新版本时复制大量控制器是很常见的,然后很容易做出如此微妙的复制粘贴错误。
答案 3 :(得分:-1)
我正在使用一种更简单的方法来测试我的控制器问题,不确定这是否是正确的方法,但似乎更简单,上面对我来说更有意义,它使用你所包含的控制器的范围。如果此方法存在任何问题,请与我们联系。 样本控制器:
class MyController < V1::BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
端
我的控制器关注:
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
关注的规格测试:
require 'spec_helper'
describe SearchFilter do
let(:ac) { V1::AppointmentsController.new }
context '#column_type' do
it 'should return :phone for phone type column' do
expect(ac.column_type('phone_daytime')).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