Periodically the question of how to test private methods comes up at work or online. My answer is: don’t. It may seem trite, but there is some reasoning behind it.
Private methods are, by definition, implementation details. If you are approaching your tests from a behavioral standpoint – and you really should be, whether you are using a full “BDD” framework or not – you should not be testing implementation, only outward behavior. For this reason alone you shouldn’t be testing private methods.
However, if you are as “test-infected” as I am you prefer to write every bit of code test-first, and sometimes you need a private method with a little more than trivial complexity. Shouldn’t you isolate it and write it test-first like any other code?
I submit that if you are writing private methods from scratch, you may be doing it wrong.
I submit that private methods should be extracted as part of a refactoring, never constructed from scratch. They should be the result of pulling working code out of a public method in order to DRY up duplication or to simplify the implementation of the public method. And since refactoring is
But what about the case of complex-yet-private code? When you have a private method which is complex enough to warrant tests of it’s own, that’s your code’s way of telling you it wants to be broken up into more classes.
An example would be good right about now. Here’s a blog post class. We want to be able to generate a “slug” version of the title – a string suitable for use as part of a URL.
class BlogPost attr_reader :title def title_slug slugify(title) end private def slugify(string) # ??? end end
We want to implement
slugify in a test-first way, giving it several different example strings and verifying it produces the expected slug. Isn’t this a case for testing a private method?
What if, instead, we broke it out into it’s own nested class?
class BlogPost attr_reader :title def title_slug SlugString.new(title) end class SlugString < String def initialize(string) # ??? end end end
Now we can test
BlogPost::SlugString to our hearts content. We haven’t resorted to ugly hacks to get around the method privacy protection. Our concerns are better separated now: BlogPost is only concerned with representing blog posts, not with text munging. And we get another benefit as well: we can easily mock out
SlugString in our tests for
BlogPost. By listening to the code when it asked for a separate class, we’ve stumbled on a better-factored design.