What's the best way to unit test protected & private methods in Ruby?

Solution 1:

You can bypass encapsulation with the send method:

myobject.send(:method_name, args)

This is a 'feature' of Ruby. :)

There was internal debate during Ruby 1.9 development which considered having send respect privacy and send! ignore it, but in the end nothing changed in Ruby 1.9. Ignore the comments below discussing send! and breaking things.

Solution 2:

Here's one easy way if you use RSpec:

before(:each) do
  MyClass.send(:public, *MyClass.protected_instance_methods)  
end

Solution 3:

Just reopen the class in your test file, and redefine the method or methods as public. You don't have to redefine the guts of the method itself, just pass the symbol into the public call.

If you original class is defined like this:

class MyClass

  private

  def foo
    true
  end
end

In you test file, just do something like this:

class MyClass
  public :foo

end

You can pass multiple symbols to public if you want to expose more private methods.

public :foo, :bar

Solution 4:

instance_eval() might help:

--------------------------------------------------- Object#instance_eval
     obj.instance_eval(string [, filename [, lineno]] )   => obj
     obj.instance_eval {| | block }                       => obj
------------------------------------------------------------------------
     Evaluates a string containing Ruby source code, or the given 
     block, within the context of the receiver (obj). In order to set 
     the context, the variable self is set to obj while the code is 
     executing, giving the code access to obj's instance variables. In 
     the version of instance_eval that takes a String, the optional 
     second and third parameters supply a filename and starting line 
     number that are used when reporting compilation errors.

        class Klass
          def initialize
            @secret = 99
          end
        end
        k = Klass.new
        k.instance_eval { @secret }   #=> 99

You can use it to access private methods and instance variables directly.

You could also consider using send(), which will also give you access to private and protected methods (like James Baker suggested)

Alternatively, you could modify the metaclass of your test object to make the private/protected methods public just for that object.

    test_obj.a_private_method(...) #=> raises NoMethodError
    test_obj.a_protected_method(...) #=> raises NoMethodError
    class << test_obj
        public :a_private_method, :a_protected_method
    end
    test_obj.a_private_method(...) # executes
    test_obj.a_protected_method(...) # executes

    other_test_obj = test.obj.class.new
    other_test_obj.a_private_method(...) #=> raises NoMethodError
    other_test_obj.a_protected_method(...) #=> raises NoMethodError

This will let you call these methods without affecting other objects of that class. You could reopen the class within your test directory and make them public for all the instances within your test code, but that might affect your test of the public interface.