Rspec、Rails:コントローラのプライベートメソッドをテストする方法?


125

私はコントローラーを持っています:

class AccountController < ApplicationController
  def index
  end

  private
  def current_account
    @current_account ||= current_user.account
  end
end

current_accountrspecでプライベートメソッドをテストする方法

PS私はRspec2とRuby on Rails 3を使用しています


8
これはあなたの質問には答えませんが、プライベートメソッドはテストされることになっています。テストでは、本当のこと、つまりパブリックAPI のみを考慮する必要があります。パブリックメソッドが機能する場合、それらが呼び出すプライベートメソッドも機能します。
Samy Dindane 2012

77
同意しません。コード内の十分に複雑な機能をテストすることには価値があります。
whitehat101 2013年

11
私も同意しません。パブリックAPIが機能する場合は、プライベートメソッドが期待どおりに機能しているとしか想定できません。しかし、仕様が偶然過ぎている可能性があります。
リミアン2014年

4
プライベートメソッドにテストが必要な場合は、テスト可能な新しいクラスにプライベートメソッドを抽出することをお勧めします。
クリス

10
@RonLuggeそうですね。後回しと経験が増えたため、3歳のコメントには同意しません。:)
Samy Dindane 2016年

回答:


196

#instance_evalを使用

@controller = AccountController.new
@controller.instance_eval{ current_account }   # invoke the private method
@controller.instance_eval{ @current_account }.should eql ... # check the value of the instance variable

94
必要に応じて、@ controller.send(:current_account)と言うこともできます。
混乱

13
Rubyではsendを使用してプライベートメソッドを呼び出すことができますが、必ずしもそうする必要はありません。プライベートメソッドのテストは、それらのメソッドへのパブリックインターフェイスをテストすることによって行われます。このアプローチは機能しますが、理想的ではありません。メソッドがコントローラーに含まれているモジュール内にあった方がよいでしょう。次に、コントローラとは別にテストすることもできます。
Brian Hogan

9
この回答は技術的には質問に答えますが、テストのベストプラクティスに違反しているため、反対票を投じています。プライベートメソッドはテストすべきではありません。Rubyがメソッドの可視性を回避する機能を提供するからといって、それを悪用する必要があるという意味ではありません。
Srdjan Pejic 2011年

24
Srdjan Pejicは、プライベートメソッドをテストすべきではない理由について詳しく説明していただけますか?
John Bachir、2011年

35
私はあなたが間違っているか、質問に答えない回答に反対票を投じると思います。この回答は正解であり、反対票を投じるべきではありません。プライベートメソッドのテストの実施に同意しない場合は、コメントにその情報を含めてください(多くの場合と同様)。他の人がそのコメントに賛成票を投じることができます。
2013

37

sendメソッドを使用します。例えば:

event.send(:private_method).should == 2

「送信」はプライベートメソッドを呼び出すことができるため


プライベートメソッド内でインスタンス変数をどのようにテストします.sendか?
the12

23

current_accountメソッドはどこで使用されていますか?それはどのような目的に役立ちますか?

一般に、プライベートメソッドをテストするのではなく、プライベートメソッドを呼び出すメソッドをテストします。


5
理想的には、すべてのメソッドをテストする必要があります。私は、rspecで大きな成功を収め、subject.sendとsubject.instance_evalの両方を使用しました
David

7
@Pullets私は同意しません。私の元の答えが言うように、プライベートのものを呼び出すAPIのパブリックメソッドをテストする必要があります。自分だけが見ることができるプライベートメソッドではなく、提供するAPIをテストする必要があります。
Ryan Bigg 2011年

5
@Ryan Biggに同意します。プライベートメソッドはテストしません。これにより、変更がコードのパブリック部分に影響を与えない場合でも、上記のメソッドの実装をリファクタリングまたは変更する機能が削除されます。自動テストを作成する際は、ベストプラクティスをお読みください。
Srdjan Pejic 2011年

4
うーん、多分私は何かが欠けています。私が作成したクラスには、パブリックメソッドよりもはるかに多くのプライベートメソッドがありました。パブリックAPIを介してのみテストすると、テスト対象のコードを反映しない数百のテストのリストが生成されます。
デビッドW.キース

9
私の理解によると、ユニットテストで実際の粒度を実現する場合は、プライベートメソッドもテストする必要があります。コードをリファクタリングする場合は、ユニットテストもそれに応じてリファクタリングする必要があります。これにより、新しいコードも期待どおりに機能することが保証されます。
インディカK

7

すべきではないプライベートメソッドを直接テストする。パブリックメソッドからコードを実行することにより、間接的にテストできます。

これにより、テストを変更せずに、コードの内部を変更することができます。


4

プライベートまたは保護されたメソッドをパブリックにすることができます:

MyClass.send(:public, *MyClass.protected_instance_methods) 
MyClass.send(:public, *MyClass.private_instance_methods)

このコードをテストクラスに配置して、クラス名を置き換えてください。該当する場合は、ネームスペースを含めます。


3
require 'spec_helper'

describe AdminsController do 
  it "-current_account should return correct value" do
    class AccountController
      def test_current_account
        current_account           
      end
    end

    account_constroller = AccountController.new
    account_controller.test_current_account.should be_correct             

   end
end

1

プライベートメソッドのユニットテストは、アプリケーションの動作との関連性が高すぎるようです。

最初に呼び出しコードを書いていますか?このコードは例では呼び出されません。

動作は次のとおりです。別のオブジェクトからオブジェクトをロードする必要があります。

context "When I am logged in"
  let(:user) { create(:user) }
  before { login_as user }

  context "with an account"
    let(:account) { create(:account) }
    before { user.update_attribute :account_id, account.id }

    context "viewing the list of accounts" do
      before { get :index }

      it "should load the current users account" do
        assigns(:current_account).should == account
      end
    end
  end
end

記述しようとしている動作からコンテキスト外にテストを記述したいのはなぜですか?

このコードは多くの場所で使用されますか?より一般的なアプローチが必要ですか?

https://www.relishapp.com/rspec/rspec-rails/v/2-8/docs/controller-specs/anonymous-controller


1

rspec-context-private gemを使用して、コンテキスト内でプライベートメソッドを一時的にパブリックにします。

gem 'rspec-context-private'

プロジェクトに共有コンテキストを追加することで機能します。

RSpec.shared_context 'private', private: true do

  before :all do
    described_class.class_eval do
      @original_private_instance_methods = private_instance_methods
      public *@original_private_instance_methods
    end
  end

  after :all do
    described_class.class_eval do
      private *@original_private_instance_methods
    end
  end

end

次に、:privateメタデータとしてdescribeブロックに渡すと、プライベートメソッドはそのコンテキスト内でパブリックになります。

describe AccountController, :private do
  it 'can test private methods' do
    expect{subject.current_account}.not_to raise_error
  end
end

0

プライベート関数をテストする必要がある場合は、プライベートメソッドを呼び出すパブリックメソッドを作成します。


3
私はあなたがこれをあなたのユニットテストコードで行うべきだとあなたが言うことを仮定します。これは基本的に、.instance_evalと.sendが1行のコードで行うことです。(そして、短いテストで同じ効果があるときに、より長いテストを書きたい人はいますか?)
David W. Keith

3
ため息、それはレールコントローラーです。メソッドはプライベートでなければなりません。実際の質問をお読みいただきありがとうございます。
マイケルジョンストン

サービスオブジェクトのプライベートメソッドをパブリックメソッドにいつでも抽象化して、そのように参照できます。このようにして、パブリックメソッドのみをテストしながら、コードをDRYに保つことができます。
ジェイソン・

0

これはちょっとハックですが、rspecでテスト可能なメソッドがprodに表示されないようにする場合に機能します。

class Foo
  def public_method
    #some stuff
  end

  eval('private') unless Rails.env == 'test'

  def testable_private_method
    # You can test me if you set RAILS_ENV=test
  end 
end

これで、実行できるときの仕様は次のようになります。

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.