ChatGPT解决这个技术问题 Extra ChatGPT

Rspec, Rails: how to test private methods of controllers?

I have controller:

class AccountController < ApplicationController
  def index
  end

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

How to test private method current_account with rspec?

P.S. I use Rspec2 and Ruby on Rails 3

This doesn't answer you question, but private methods aren't supposed to be tested. Your tests should only care about the real thing - your public API. If your public methods work, then the private ones they call also work.
I disagree. There is value in testing any sufficiently complex feature in your code.
I disagree also. If your public API works, you can only assume your private methods are working as expected. But your specs might be passing by coincidence.
It would be better to extract the private method in to a new class which is testable, if the private method needs testing.
@RonLugge You're right. With more hindsight and experience, I disagree with my three years old comment. :)

m
monocle

Use #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

If you prefer, you can also say: @controller.send(:current_account).
Ruby lets you call private methods with send, but that doesn't necessarily mean you should. Testing private methods is done through testing the public interface to those methods. This approach will work, but it's not ideal. It'd be better if the method was in a module that was included into the controller. Then it could be tested independently of the controller as well.
Even though, this answer technically answers the question, I'm downvoting it, because it violates best practices in testing. Private methods should not be tested, and just because Ruby gives you the ability to circumvent method visibility, it doesn't mean that you should abuse it.
Srdjan Pejic could you elaborate on why private methods should not be tested?
I think you downvote answers that are wrong or don't answer the question. This answer is correct and shouldn't be downvoted. If you disagree with the practice of testing private methods, put that in the comments as it is good info (as many did) and then people can upvote that comment, which still shows your point without unnecessarily downvoting a prefectly valid answer.
P
Perception

I use send method. Eg:

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

Because "send" can call private methods


How would you test instance variables within the private method using .send?
R
Ryan Bigg

Where is the current_account method being used? What purpose does it serve?

Generally, you don't test private methods but rather test the methods that call the private one.


Ideally one should test every method. I have used both subject.send and subject.instance_eval with much success in rspec
@Pullets I disagree, you should be testing the public methods of the API which will be calling the private ones, as my original answer says. You should be testing the API you provide, not the private methods only you can see.
I agree with @Ryan Bigg. You do not test private methods. This removes your ability to refactor or change the implementation of said method, even if that change doesn't affect the public parts of your code. Please read up on best practices when writing automated tests.
Hmm, maybe I am missing something. The classes I write had many more private methods than public ones. Testing only through the public API would result in a list of hundreds of tests that don't mirror the code they are testing.
According to my understanding if you want to achieve real granularity in unit testing private methods also should be tested. If you want to refactor code unit test also has to be refactored accordingly. This assures your new code also works as expected.
X
X.Creates

You should not test your private methods directly, they can and should be tested indirectly by exercising the code from public methods.

This allows you to change the internals of your code down the road without having to change your tests.


z
zishe

You could make you private or protected methods as public:

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

Just place this code in your testing class substituting your class name. Include the namespace if applicable.


j
jschorr
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

B
Brent Greeff

Unit testing private methods seems too out of context with the behaviour of the application.

Are you writing your calling code first? This code is not called in your example.

The behaviour is: you want an object loaded from another object.

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

Why do u want to write the test out of context from the behaviour you should be trying to describe?

Does this code get used in a lot of places? Need a more generic approach?

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


b
barelyknown

Use the rspec-context-private gem to temporarily make private methods public within a context.

gem 'rspec-context-private'

It works by adding a shared context to your project.

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

Then, if you pass :private as metadata to a describe block, the private methods will be public within that context.

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

o
onetwopunch

I know this is kinda hacky, but it works if you want the methods testable by rspec but not visible in 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

Now when you can run you're spec like so:

RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 

z
zishe

If you need to test a private function create a public method that invokes the private one.


I assume you mean that this should be done in your unit test code. That is essentially what .instance_eval and .send do in a single line of code. (And who wants to write longer tests when a shorter one has the same effect?)
sigh, it's a rails controller. The method has to be private. Thanks for reading the actual question.
You can always abstract the private method to a public method in a service object and reference it that way. That way you can have test only public methods, and yet still keep your code DRY.