web-dev-qa-db-ja.com

Rails 4でController Concernをテストする方法

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.
39
jelder

共有された例を使用し、含まれているコントローラーのスコープで実行するように指示する多くのアドバイスがあります。

私は個人的にそれが過剰殺害だと感じ、単体テストを単独で実行し、統合テストを使用してコントローラーの動作を確認することを好みます。

方法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
_

注:懸念事項が含まれていることをテストし続ける

また、懸念クラスがターゲットクラスに含まれていることをテストすることも重要です。1行で十分です。

_describe SomeTargetedController do
  describe 'includes MyControllerConcern' do
    it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
  end
end
_
80
Benj

最も投票された回答から方法2を簡略化します。

anonymous controller rspecでサポート http://www.relishapp.com/rspec/rspec-Rails/docs/controller-specs/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を記述し、デフォルトでこれが発生しない場合にタイプを設定する必要があることに注意してください。

25
Calin

私の答えは、@ Benjと@Calinによるこれらよりも少し複雑に見えるかもしれませんが、それには利点があります。

_describe Concerns::MyConcern, type: :controller do

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

  # your tests go here
end
_

まず、ApplicationControllerではなく_ActionController::Base_のサブクラスである匿名コントローラーを使用することをお勧めします。アプリケーションで定義されている他のベースコントローラーも使用しません。このようにして、どのコントローラーからも隔離して懸念事項をテストできます。一部のメソッドがベースコントローラーで定義されると予想される場合は、それらをスタブするだけです。

さらに、コピーアンドペーストエラーを回避するのに役立つため、懸念モジュール名の再入力を避けることをお勧めします。残念ながら、controller(ActionController::Base)に渡されたブロックでは_described_class_にアクセスできないため、_#tap_メソッドを使用して、ローカル変数に_described_class_を格納する別のバインディングを作成します。これは、バージョン管理されたAPIを使用する場合に特に重要です。そのような場合、新しいバージョンを作成するときに大量のコントローラーをコピーすることは非常に一般的であり、そのような微妙なコピーアンドペーストの間違いは非常に簡単です。

3
skalee