Rspec,Rails:如何测试控制器的私有方法?


125

我有控制器:

class AccountController < ApplicationController
  def index
  end

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

如何current_account使用rspec 测试私有方法?

PS我使用Rspec2和Ruby on Rails 3


8
这不能回答您的问题,但是私有方法不应该被测试。您的测试应该只关心真实的东西 -您的公共API。如果您的公共方法有效,那么它们调用的私有方法也将有效。
Samy Dindane 2012年

77
我不同意。测试代码中任何足够复杂的功能很有价值。
whitehat101

11
我也不同意。如果您的公共API有效,则只能假定您的私有方法正在按预期工作。但是,您的规格可能是巧合。
Rimian 2014年

4
如果私有方法需要测试,最好将私有方法提取到可测试的新类中。
克里斯(Kris)2014年

10
@RonLugge你是对的。有了更多的事后观察和经验,我不同意我三岁的评论。:)
Samy Dindane'3

Answers:


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

24
Srdjan Pejic您可以详细说明为什么不应该测试私有方法吗?
John Bachir

35
我认为您否决了错误的答案或没有回答问题。这个答案是正确的,不应被否决。如果您不同意测试私有方法的做法,则将其作为好信息(就像很多人一样)放入评论中,然后人们可以对该评论进行投票,这仍然可以表明您的观点而不必不必要地对完全有效的答案进行投票。
2013年

37

我使用发送方法。例如:

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

因为“发送”可以调用私有方法


您将如何使用private方法测试实例变量.send
the12

23

在哪里使用current_account方法?它有什么作用?

通常,您不测试私有方法,而是测试调用私有方法的方法。


5
理想情况下,应该测试每种方法。我在rspec中使用了subject.send和subject.instance_eval都取得了很大的成功
David W. Keith

7
@Pullets我不同意,您应该测试API的公共方法,该方法将调用私有方法,正如我的原始回答所说。您应该测试提供的API,而不是只能看到的私有方法。
瑞安·比格

5
我同意@Ryan Bigg。您不测试私有方法。即使您的更改不会影响代码的公共部分,这也会消除您重构或更改该方法的实现的能力。编写自动化测试时,请阅读最佳实践。
Srdjan Pejic

4
嗯,也许我想念一些东西。我编写的类比公共方法拥有更多的私有方法。仅通过公共API进行的测试将产生数百个测试的列表,这些测试不会镜像正在测试的代码。
David W. Keith

9
根据我的理解,如果要在单元测试中实现真正的粒度,还应该测试私有方法。如果要重构代码单元测试,还必须相应地重构。这样可以确保您的新代码也可以按预期工作。
Indika 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在一行代码中所做的。(谁希望在较短的测试具有相同效果时编写更长的测试?)
David W. Keith

3
叹息,这是一个轨道控制器。该方法必须是私有的。感谢您阅读实际问题。
迈克尔·约翰斯顿,

您始终可以将私有方法抽象为服务对象中的公共方法,然后以这种方式引用它。这样,您就可以仅测试公共方法,而仍然保持代码DRY。
杰森

0

我知道这有点hacky,但是如果您希望方法可以通过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 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.