61

I got a model with a private method I'd like to spec with RSpec,
how do you usually do ? Do you only test the method calling the private one ?
or also spec the private one ? if so, how do you do ?

Mike
  • 5,165
  • 6
  • 35
  • 50

4 Answers4

133

I always take this approach: I want to test the public API my class exposes.

If you have private methods, you only call them from the public methods you expose to other classes. Hence, if you test that those public methods work as expected under all conditions, you have also proven that the private methods they use work as well.

I'll admit that I've come across some especially complex private methods. In that extreme case you want to test them, you can do this:

@obj.send(:private_method)
Ariejan
  • 10,910
  • 6
  • 43
  • 40
  • 1
    Thanks for your help, I agree that testing the public method proves the private one works too. That being said here I really want to test the private method and your solution works like a charm, Thanks ! – Mike Nov 11 '10 at 13:25
  • 3
    Here's another interesting approach http://kailuowang.blogspot.com/2010/08/testing-private-methods-in-rspec.html – gucki Feb 14 '12 at 09:58
  • 1
    I have a before_save hook in my model that saves a private token to the database via a private 'make_private_token' method. The 'make_private_token' method uses public class methods, not the other way around, so the only way I can test it is with the `send` method. Don't see anything `extreme` about that... – Starkers Jan 27 '14 at 22:54
  • 4
    What about args if the method require some arguments? I'll try with `@obj.send(:private_method, arg1, arg2)` – Vadorequest Jan 29 '14 at 09:38
  • 3
    I realize this answer is very old, but I don't think proving the public methods work implies that the private methods they call work. Couldn't there be false positives where incorrect logic in both places happens to cancel each other out? Here's an example: http://pastebin.com/Zydk9e6W – istrasci Jul 19 '16 at 22:58
  • I don't agree with that idea of aiming to test the public methods. It leads to bloated specs, making it harder to to maintain and understand. One should assures wether and when public methods calls private methods, but specing their all nuances of implementation should be unit test apart. – Andre Figueiredo Jul 12 '19 at 18:18
  • simple does it! :) – Tilo Nov 09 '21 at 23:35
6

For the private methods that need code coverage (either temporarily or permanently), 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.

class Example
  private def foo
    'bar'
  end
end

describe Example, :private do
  it 'can test private methods' do
    expect(subject.foo).not eq 'bar'
  end
end
barelyknown
  • 5,510
  • 3
  • 34
  • 46
  • I don't know if you maintain this gem any more, but your documentation does not have the code block about declaring the shared_context. So I was unable to get it to work simply by reading the documentation. After adding in this block, it is working as expected. – istrasci Jul 20 '16 at 16:46
2

create a dummy class and access private method using .send(:private_method, args)

example

obj = Class.new { extend Classname } obj.send(:sum, 1,2)

obj = Class.new { extend Classname } obj.send(:sum)

sonal save
  • 98
  • 7
0

If you're wanting to test an expectation on a private method, the accepted answer won't really work (at least not that I know of, so I'm open to correction on that point). What I've done instead is even filthier - in the test itself, just expose the method by redefining it:

def object_to_test.my_private_method
  super
end

Works on Ruby 1.8, can't comment on any of the newer runtimes.

Nathan Crause
  • 874
  • 10
  • 7