Skip to content

testing protected and private methods in ruby

When I was looking for how to test protected an private methods in ruby on the net, I found many sites arguing whether you should, and several methods for doing so. I am of the opinion that if your method contains any logic at all, it should have a test. Some examples of what I consider logic:

Object:

class User
  validates_presence_of :first_name, :last_name, :company_id
  belongs_to :company
  def full_name
    "#{last_name}, #{first_name}"
  end
  def company_name
    company.name
  end
end

Test:

class UserTest < Test::Unit
  def test_full_name
    assert_equal("Gaffney, Mike", User.new(:first_name => "Mike", :last_name => "Gaffney"))
  end
  def test_company_name
    company = Company.new(:name => "CompanyName")
    user = User.new(:company => company)
    assert_equal("CompanyName", user.company_name)
  end
end

This may seem like overkill but when many people are looking at the code it greatly helps communicate intention to others.

Testing:

I also found several methods to test protected and private methods in ruby, so I will cover the pluses and minuses the ones I know of.

Lets say we have a User class and an unassociated comments class. Comments are only attributed to a poster’s full name. There is no direct ID link between comments and users.

class User
  validates_presence_of :first_name, :last_name
  def find_comments
    Comments.find(:all, :conditions => {:poster => full_name})
  end
  protected
  def full_name
    "#{last_name}, #{first_name}"
  end
end

Here are the approaches I’m going to use to test this:

  • Using a Mock to open access restrictions
  • Make single (protected/private) methods public on the tested class.
  • Make all (protected/private) methods public on the tested class.
  • Hybrid Mock Approach
  • Using instance_eval and send
  • send

Using Mocks:
Mocks should be pretty familiar to most developers. Basically you create a class or subclass that the test will use. In our case we are simply opening the access restrictions with a subclass. Our test class looks like this:

class MockUser < User
  def full_name
    super
  end
end
class UserTest < Test::Unit
  def test_full_name
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

Plusses:

  • Simple and familiar to most developers from many languages

Minuses:

  • Can’t test private methods (subclass doesn’t have access).
  • With complicated classes you end up with quite a few mocks for all of the different cases. Managing the mocks becomes tedious.

Make single methods public:
Next we will make use of the runtime nature of ruby to change a function from protected to public. This also works for private methods:

class UserTest < Test::Unit
  def test_full_name
    User.send(:public, :first_name)
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

or

class User
    public :first_name
end
class UserTest < Test::Unit
  def test_full_name
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

Plusses:

  • Lets us directly access the private or protected function.

Minuses:

  • This pollutes the default namespace for all of the other tests. Remember that tests can (and should be able to) run in random order. Take for example:
    1. Full Name is a public function that some other objects use.
    2. We make this function into a protected one and use this method to make it public for testing.
    3. If this new test runs before the tests for the objects using the old public full_name, full_name will still be public even though it is actually protected.
    4. The other tests do not fail but the code will fail during runtime.

Make all methods public:
To save us some time on a big class, we can make all private and protected methods public:

class User
  public *protected_methods.collect(&amp;amp;amp;:to_sym)
  public *private_methods.collect(&amp;amp;amp;:to_sym)
end
class UserTest 
  def test_full_name
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

or

class UserTest < Test::Unit
  def test_full_name
    User.send(:public, *MyClass.protected_instance_methods)  
    User.send(:public, *MyClass.private_instance_methods)
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

This has the same issues as above but can be done one to make large classes easy to test, especially using the first method.

Hybrid Approach:
This works like the mock example and the previous example combined:

class AllAccessUser
  public *protected_methods.collect(&amp;amp;amp:to_sym)
end
class UserTest < Test::Unit
  def test_full_name
    user = AllAccessUser.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.full_name)
  end
end

This is easy like the previous but does not pollute the namespace. However it cannot be used for private methods.

Using send
Now we will use the built in reflection method to call the protected/private method on the class. This makes use of the fact that protected and private in ruby aren’t really like protected and private in other languages.

class UserTest < Test::Unit
  def test_full_name
    user = User.new(:first_name => "Mike", :last_name => "Gaffney")
    assert_equal("Gaffney, Mike" user.send(:full_name))
  end
end

Plusses:

  • Doesn’t pollute the namespace.
  • All code is right in the test.
  • Works for protected and private.

Minuses:

  • Some people don’t like using send

Summary:
Testing protected and private methods should be done, and ruby makes it much easier than some other languages. My preferred technique is the final one of using send. It is slightly less readable but keeps everything contained in one location.

14 Comments

  1. Shane wrote:

    I find if you test the public interface only everything else will be exercised in the process without bypassing access control. If you have any abstract methods implement one or more final subclasses and test those.

    This method works for any language, doesn’t break encapsulation and with a good test coverage tool can show you possibly unused code.

    Monday, October 27, 2008 at 5:16 pm | Permalink
  2. I sometimes use the following in the test case setup for the klass being tested …

    http://gist.github.com/20349

    Tuesday, October 28, 2008 at 4:10 am | Permalink
  3. Luke Redpath wrote:

    I still can’t see the argument for testing private/protected methods; they must be being called from somewhere in your public API so just test through the public methods.

    If you find you have lots of private methods then there might be a hidden class trying to get out; it is then best to extract the new class and test it separately (many of the private methods extracted into this new class will then form it’s public api).

    Performing this refactoring is difficult if you’ve written all of your tests for your original class around these private/protected methods – if you’ve gone through your public API, you should be able to refactor out this new class without breaking anything.

    This for me is the biggest reason *not* to test private/protected methods directly (and if possible, avoid stubbing them too, and never set expectations on them).

    Tuesday, October 28, 2008 at 4:34 am | Permalink
  4. Radarek wrote:

    Are you sure that super.full_name works for you? “super” means “call the same method but with definition from next ancestor of this class” and you call “full_name” on returned value by super.

    http://pastie.org/302159

    Tuesday, October 28, 2008 at 4:50 am | Permalink
  5. Radarek wrote:

    Also instead of:
    class UserTest << Test::Unit
    it should be
    class UserTest < Test::Unit

    Tuesday, October 28, 2008 at 4:51 am | Permalink
  6. Amos wrote:

    There is nothing wrong with testing private methods. In fact testing of every method in a class is a great practice. It all depends on your tolerance level for typing more code in your tests. At some point you will get tired of having to jump through hoops on testing and it will drive you to refactor that section. If you aren’t testing the methods directly you may not notice the amount of code you in private methods.

    Instead of making a method public for testing use instance_eval:

    class UserTest “Mike”, :last_name => “Gaffney”)
    assert_equal(“Gaffney, Mike”, user.instance_eval {full_name})
    end
    end

    Decide what is the best way for you to know when it is time to refactor.

    Tuesday, October 28, 2008 at 8:00 am | Permalink
  7. Luke Redpath wrote:

    “In fact testing of every method in a class is a great practice.”

    I find this assertion to be completely wrong and bad advice.

    It’s not about testing methods, it’s about testing behaviour. Your behaviour should be exercised through it’s public interface (just like your client code) otherwise what’s the point of access control? You should be able to free to refactor the internals of your class (private methods) without breaking anything, including your unit tests. Private methods aren’t behaviour, they are pure implementation detail. As I said before; if you find a lot of stuff happening in your private methods, then maybe there is a hidden class waiting to be extracted. Extract it, then test the new classes public interface.

    Remember, refactoring is the process of changing the code without changing it’s behaviour; if you test the code at its lowest level (private methods) instead of testing the behaviour you are testing the wrong thing and creating strong coupling between your tests and your code, making refactoring harder.

    Tuesday, October 28, 2008 at 11:05 am | Permalink
  8. Luke Redpath wrote:

    Just to clarify, the reason I find that assertion to be bad advice is because it could lead to the assumption that just because you’ve written a test “for every method”, that your class is well tested, when it may not be.

    The fact that you have to jump through hoops to test private methods should tell you that it’s probably not the right thing.

    Tuesday, October 28, 2008 at 11:06 am | Permalink
  9. Amos King wrote:

    I concede the fact that testing private methods may be driving design too much, and can lead to painful refactoring. Painful refactoring may lead to worse code in the end.

    If you have private methods, test them! If you aren’t testing private methods move them to another class and test that. This is a much better practice and may lead to more reused code.

    Tuesday, October 28, 2008 at 12:48 pm | Permalink
  10. Luke Redpath wrote:

    If you’re testing your public methods, and they are invoking your private methods, then your private method code *is* being testing, that’s the point. Testing private methods directly (out of context) has no value at all.

    Tuesday, October 28, 2008 at 4:33 pm | Permalink
  11. Alex Pooley wrote:

    Private methods exist to capture common code within a class. Assuming you may use that private method again it’s nice peace of mind to know that it’s logically correct.

    You may not exercise the private method via the current class interface, but at a later date you may add another method and you may find an issue in your private method indirectly while testing your new method. I would rather catch the problem with the private code right after writing it than fuddling through an error on an indirect method call a few months later.

    Why not test both if you have the luxury? :)

    Tuesday, October 28, 2008 at 6:47 pm | Permalink
  12. gaffo wrote:

    Radarek: Thanks. I didn’t run this through an interpreter before I posted it. Good Eye.

    Tuesday, October 28, 2008 at 7:46 pm | Permalink
  13. Luke Redpath wrote:

    “Why not test both if you have the luxury?”

    Because of all of the problems I mention above. If you have code paths that aren’t being exercised by a public API, then delete them.

    Another point is: if you’re writing private methods up front rather than extracting them from (already tested) public methods, then you’re probably making design decisions too early. If you’re following a TDD approach, you’re writing the simplest thing that works then refactoring later – this would typically involve extracting common code into private methods (to remove duplication or introducing explaining methods) – but the behaviour hasn’t changed and you already have tests in place that go through your public api. At this point, adding direct tests for these extracted private methods gives you nothing; they should already be covered.

    Again, as I said before, if you have a large number of private methods that seem to be doing a lot of related things, then extract them into a new class and test that as an isolated unit.

    I’m yet to see any convincing arguments as to why you should break encapsulation and test private methods directly but I think I’ve presented numerous reason why you shouldn’t – high coupling between tests and implementation leading to difficult refactoring, obscuring poor design decisions and redundant test code.

    Your client code will only be (or should be) using your class under test through it’s public API – your tests should do the same.

    Wednesday, October 29, 2008 at 2:47 am | Permalink
  14. fhinkel wrote:

    You can just use a singleton method. It’s 3 more lines of code in your test class and requires no changes in the actual code to be tested

    def user.full_name_publicly (*args)
    full_name(*args)
    end

    then you can use in the test cases
    assert_equal “fullname”, user.full_name_publicly

    http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html

    Tuesday, January 5, 2010 at 10:55 pm | Permalink

Post a Comment

Your email is never published nor shared.