Unit tests are easy to write for methods that are publicly accessible. However, when it becomes necessary to test methods with modifiers other than public, things tend to get rather complicated. This article presents a brief overview of some of the techniques involved in achieving this.
What does the book say about this particular problem? The Art of Unit Testing essentially says that it is okay to break your class’s encapsulation and expose its inner members if it makes the class testable. Quoting from Chapter 3:
“By now, you may be thinking to yourself that adding all these constructors, setters, and factories for the sake of testing is problematic. It breaks some serious object-oriented principles, especially the idea of encapsulation, which says, “Hide everything that the user of your class doesn’t need to see.” That’s our next topic. (Appendix A also deals with testability and design issues.)
“Some people feel that opening up the design to make it more testable is a bad thing because it hurts the object-oriented principles the design is based on. I can wholeheartedly say to those people, “Don’t be silly.” Object-oriented techniques are there to enforce some constraints on the end user of the API (the end user being the programmer who will use your object model) so that the object model is used properly and is protected from unforeseen ways of usage. Object orientation also has a lot to do with reuse of code and the single-responsibility principle (which requires that each class has only a single responsibility).
“When we write unit tests for our code, we are adding another end user (the test) to the object model. That end user is just as important as the original one, but it has different goals when using the model. The test has specific requirements from the object model that seem to defy the basic logic behind a couple of object-oriented principles, mainly encapsulation. Encapsulating those external dependencies somewhere without allowing anyone to change them, having private constructors or sealed classes, having nonvirtual methods that can’t be overridden: all these are classic signs of overprotective design. (Security-related designs are a special case that I forgive.) The problem is that the second end user of the API, the test, needs them as a feature in the code. I call the design that emerges from designing with testability in mind testable object-oriented design (TOOD), and you’ll hear more about TOOD in Appendix A.”
So the guy who wrote the book is basically telling us: “Don’t be silly. It’s okay to piss on the several-decade-old discipline of Object Oriented Programming if it lets us test our classes. In fact, I’m going to invent my own design that allows it.”
A twist on publicly exposing a class’s internals (which the book also mentions) is to make them internal and use InternalsVisibleTo. Although more restrictive, this practice is no less dangerous.
Breaking a class’s encapsulation and opening it up for public access defies the whole point of encapsulation: to restrict the access to certain data so that (a) it can be changed only via well-defined channels and is protected against corruption, and (b) it can be refactored without affecting lots of code that relies on it (“client code”). If a test can gain access to those internals, then so can regular code that shouldn’t be able to see it.
On the other end of the spectrum, there are those who don’t want to break encapsulation, but because of some religious obligation towards [insert test-related methodology here] they have to have 100% unit test coverage, including public methods, protected, internal, private, and even those written by
Chuck Norris Jon Skeet which are pointless to test because they can be axiomatically taken to be correct.
Such people often go to great lengths in order to test their private methods, and their approach of choice usually involves Reflection. (A more recent variant is to use dynamic.) Here are some approaches I’ve seen:
- Unit testing non-public types using reflection (MSDN, 2008).
- An example using PrivateObject for the same thing (2012).
- Use C# dynamic typing to conveniently access internals of an object (2010), and the ExposedObject it uses.
While these approaches will allow you to test the internals of a class without breaking encapsulation per se, they are pretty messy and will make your tests hard to maintain.
More importantly, however, the very fact that the private members of a class need to be manipulated in order to write unit tests is often the sign of a flawed design, which brings us to…
So why do we need to test these class internals anyway? Actually, in principle, it’s fairly reasonable. Your class has a private helper method, say, to validate an email address. So why not write unit tests to ensure that that particular code is behaving correctly, before testing the class as a whole?
This is where the book quoted earlier kind of has a point. It is right in suggesting that sometimes our designs may be overprotective. This does not mean we should break encapsulation; it only means we should reconsider our design to ensure that we are following object oriented principles correctly.
So when we go back and look at our private email-validating method, is there really any reason why it should be locked up in a class? Is it so unreasonable for it to be taken out into its own EmailValidator class and used in other parts of the software? Does it really belong in the original class in the first place? Think Single Responsibility Principle. There’s a reason why serialization logic is usually not encapsulated in model classes, for example.
In general it should be sufficient to write unit tests against only the public interfaces of our classes, and careful design often enables this. This allows us to maintain encapsulation where it makes sense while at the same time making it unnecessary to resort to unorthodox techniques to test every last line of code, private or otherwise.
Naturally, there are exceptions to this, and sometimes particular use cases will mandate the need to unit test private methods. That’s okay, and that’s why it’s important to evaluate methodologies according to the need you have at the time. Software engineering is too young an industry for there to be hard-and-fast rules.