I was under the impression that the question of whether to write unit tests for private methods or not has been already clearly answered. However, it appears that there are some contradictory views on the topic.
So, the short and simple answer to the question is: YES, we should write tests for private methods!
The problem is, though, that those who ask this question don’t quite realize that it’s much more complex than it initially seems to be. Let us have a closer look at it now.
The existence of a private method makes sense only when it performs a task assigned by public methods. It can be called directly by one of the public methods or indirectly by a calling chain of other public and private methods. But the first method called must always be a public one as an entry point to the functionality offered by a given class.
So if we diligently and fully test a functionality offered by public methods, we also test the private methods, although indirectly. If our private methods are small and simple, the indirect testing via public methods is perfectly sufficient. All we have to do is make sure that the set of tests for the public methods fully covers the private methods, too.
The problem is what to do when the private methods are neither small nor simple and include some more logic, some conditional instructions, loops and references to other services. In such a case, the indirect tests might be too complex to write and too hard to interpret.
The tests are becoming too complicated when the implementation of even a single test case requires mocking f too many dependencies, preparing to many types of input data, checking the occurrence (or lack thereof) of multiple side effects, and interpreting too complex output data.
Another problem is that the error reported by such a test might be difficult to interpret if we are unable to immediately identify its cause. Is it the public method directly called in the test? Is this one of the private methods called indirectly? If so, which one? If the test checks a number of methods called indirectly, we lose the definite link between the test and the erroneous code.
These are the reasons tempting us to directly test the private method (or a number of them coexisting as part of a functionality) instead of struggling with writing test cases working indirectly via public methods. Our testing tools give us the means to do this with just a couple of tricks.
This approach lets us delude ourselves that we’ve done our job well as the functionality has been tested, but it does not solve the underlying problem which tempted us to open the box with tricks that allow us to call private methods.
The problem is not that we have no access to private methods but rather that these methods have become too numerous and too complex. Possibly, they also obscure the view of the tested class and diffuse responsibility while, as we know, it’s best to limit the responsibility to a single task.
When a class begins to contain too many too complicated private methods, causing problems with testing such a class via its public interface, we should move (at least some of) these methods to a new class. Just remember to give it a name that reflects its limited task. In the new class these methods will be public so they can be used by the class they have been moved out of. Once they’re public, they can be easily tested directly.
Failing to make these changes and continuing testing private methods directly, we tie our tests to the current implementation of the tested class. A a result, we test how our class carries out the assigned functionality instead of testing what it carries out. Calling private methods directly, we freeze the implementation of the tested class and we kill the possibility of changing it in the future. The tests will report errors even when there have been no changes in the functionality of the class itself, but there has been a modification or refactoring only of the way the functionality is carried out. Tests of this sort are too sensitive and their existence is pointless.
So again, the answer to the question whether we should use unit tests for private methods is YES! If they’re simple and not numerous, test them indirectly. Otherwise, move them as public to a new class where they can be tested directly.
Don’t justify the negligence to refactor/simplify/clean the code with arguments like ‘it’s too much work’, ‘it’s too difficult and time-consuming’, ‘we’re just testing a single small private method right now’.
We are software engineers and I think we know how to do such simple refactoring safely. If we’re not sure, we can always ask our more experienced colleagues for help.
We’re also responsible programmers aware of the long-term benefits of maintaining healthy, clean, and effectively tested code. Do not waste your chances of improving it.
Video for today: The Magic Tricks of Testing by Sandi Metz