Testing private methods

by Jason Swett,

A question that has befuddled many developers, including myself for a long time, is: how do I test private methods? Should I test private methods?

My opinion is yes, but not directly. I test the behavior of a class’s private methods indirectly through the class’s public methods. In other words, I test my private methods the exact same way my private methods are used by the rest of the application.

The reasoning with my testing approach has to do with the reason why private methods exist. In my mind the value of private methods is that since a private method is hidden from the outside world I can feel free to refactor the private methods at any time and in any way, knowing that I’m not going to mess up any of the code that uses my class.

Here’s a concrete example of a class makes what I think is appropriate use of private methods:

class TypeaheadTag < ActionView::Helpers::Tags::Base
  def render
    hidden_field + text_field_tag
  end

  private

  def hidden_field
    @template_object.hidden_field(
      @object_name,
      "#{@method_name}_id",
      class: "#{@method_name}_id"
    )
  end

  def text_field_tag
    @template_object.text_field_tag(
      @method_name,
      value,
      class: "#{@options[:class]} #{@method_name}_typeahead"
    )
  end
end

I wrote this class to DRY up Twitter Bootstrap typeahead components, which appeared in many places in an application I was developing. The existence of this class (along with other supporting code) allows me to spit out a typeahead field as succinctly as this:

<%= form.typeahead :person, class: 'form-control' %>

The exact manner in which this typeahead class conjures up its necessary components – a hidden field and a text field – is its own private business. No external party needs to know or care how these things happen. The class should be free to refactor these methods, split these two methods into several methods if desired, or combine them all into one, all without altering its public interface one bit.

This principle is why I think it’s useful for tests not to have any knowledge of a class’s private methods. Again, it’s not that the behavior inside the private methods doesn’t get tested – it does. It just gets tested via the class’s public interface.

5 thoughts on “Testing private methods

  1. James Couball

    Hi, Jason!

    I totally agree that writing tests for private methods is OVER TESTING.

    I might write tests for private methods while I am writing that method. I generally only do this for complicated methods (which itself might be a code smell) but I delete them once I am done.

    In the end, I use my code coverage metrics to make sure that the tests through the class’s public interface test ALL the code in the class.

    This all assumes that you are correctly classifying methods as public vs. private which I am pretty strict about.

    Sandy Metz has a great talk that goes into more details on this subject called “The Magic Tricks of Testing”
    https://www.youtube.com/watch?v=URSWYvyc42M

    Reply
  2. tommy

    Hi,
    I agree that you dont need to test private methods in Rails directly, but sometimes it is hard to recognize where is the issue in your class and sometimes I test private methods. This is pretty easy thing to do btw. Imagine you have class:
    class Something
    def do_something
    #
    end

    private

    def do_something_else
    #
    end
    end

    Then you can test it in rspec like this:

    expect(Something.new.send(:do_something_else).to eq(‘something’)

    Then you know that this method is doing when is intended to do. In some situations, for example when you use couple private methods to get some result is is easier to control if ech of the method does what it should when you test each one of them.

    Reply
  3. Gio

    Nice post Jason, thanks for sharing.

    After helping writing correct code, tests remain in the codebase as a safety net to ensure behaviour is not changed by accident. They allow us to change the implementation of the software and be certain we haven’t broken its behaviour.

    If every method were to be tested then we would have to change tests every time we refactor. This would result in a lot of extra work, giving solid ground to the “but writing tests slow me down” complaint.

    As you say, we should test only the **public interface** of our classes, allowing the developers to change their internal implementation as they please easily, i.e. without having to touch any tests.

    After all, that’s what refactoring is all about, changing how the code _looks_ without changing what it _does_. If we have to change the tests then we’re changing the behaviour, and that’s a rewrite, not a refactor.

    Reply
      1. Ian

        Counterpoint: Assume my public method makes use of several private methods that are each quite complex. If I do not test the private methods in isolation, and instead test them through the public method, the number of test cases I must write to ensure coverage can grow unwieldy very quickly.

        For example, say I have only have two private methods to keep it simple, each taking 3 boolean arguments, and none of the values are shared between the methods:

        def m1(a, b, c)
        # complex code
        end

        def m2(d, e, f)
        # complex code
        end

        If I test them separately, I will need exactly 2^3 + 2^3 = 16 test cases to make sure my coverage is complete. If I test them only through the public interface, I would need at *least* 16 test cases, though possibly as *many* as 2^6 = 64 to make sure I’ve covered all logic paths.

        Unless there is a hole in my logic, this seems like a good reason to test the private methods independently.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *