簡體   English   中英

如何在 Rails 4 中測試控制器關注點

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

在 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.

您會發現許多建議告訴您使用共享示例並在您包含的控制器范圍內運行它們。

我個人覺得這太過分了,更喜歡單獨執行單元測試,然后使用集成測試來確認我的控制器的行為。

方法 1:不進行路由或響應測試

創建一個假控制器並測試它的方法:

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

方法二:測試響應

當您關注的問題包含路由或您需要測試響應、渲染等時……您需要使用匿名控制器運行您的測試。 這允許您訪問所有與控制器相關的 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

如您所見,我們使用controller(ApplicationController)定義了匿名控制器。 如果您的測試涉及除ApplicationController之外的另一個類,您將需要對此進行調整。

同樣為了使其正常工作,您必須在spec_helper.rb文件中配置以下內容:

config.infer_base_class_for_anonymous_controllers = true

注意:繼續測試您的擔憂是否包括在內

測試您的關注類是否包含在您的目標類中也很重要,一行就足夠了:

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

從投票最多的答案中簡化方法 2。

我更喜歡 rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller 中支持的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

請注意,您需要描述ApplicationController並設置類型,以防默認情況下不會發生這種情況。

我的答案可能看起來比@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定義的任何其他基本控制器。 通過這種方式,您可以獨立於任何控制器來測試關注點。 如果您希望在基本控制器中定義某些方法,只需存根它們即可。

此外,避免重新輸入關注模塊名稱是一個好主意,因為它有助於避免復制粘貼錯誤。 不幸的是,在傳遞給controller(ActionController::Base)的塊中無法訪問described_class ,因此我使用#tap方法創建另一個綁定,將described_class存儲在局部變量中。 這在使用版本化 API 時尤其重要。 在這種情況下,在創建新版本時復制大量控制器是很常見的,然后很容易犯這種微妙的復制粘貼錯誤。

我正在使用一種更簡單的方法來測試我的控制器問題,不確定這是否是正確的方法,但似乎比上面更簡單並且對我有意義,它使用包含的控制器的范圍。 如果此方法有任何問題,請告訴我。 樣品控制器:

class MyController < 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)    { 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