Let’s try to answer one of the most common questions in unit testing - how to test private methods?
"One of the biggest misconceptions in unit testing is that when you test a class, you should test each and every method in it" - Vladimir Khorikov
To test private methods you would usually choose one of the following approaches:
- Use a more powerful library, like PowerMock, that allows you to test private methods.
Or use reflection by yourself. - Make the method’s access modifier looser, for example, change private → public, which allows you to test the method normally.
You can think of public methods as the class’s APIs and private methods as the implementation details.
As with regular APIs, the implementation is the one that frequently changes, whereas the APIs reminds pretty stable.
In the first approach, the tests know too much about the internal details of a class, and when it happens -the tests become fragile, frequently failing even though the observable behavior of the class does not change.
Tests should not have special privileges, they should interact with the class the same way the clients do.
This allows you to test only the logic that can be executed.
It also allows you to write the private methods in a way that contains only the needed logic - code coverage will tell you what parts of the private method are not covered, and if these parts are unreachable, then consider removing them.
Private methods that contain logic that is never used add more things to maintain and increase the probability that something will break.
But what about the second approach? you can change the private methods to public and the problem is solved.
Let’s see why this approach is even more dangerous than the first one.
The second approach is talking about breaking the class’s encapsulation in order to allow testing of private methods.
We are all familiar with one of OOP’s main concepts, encapsulation, and it is clear to us why it is a bad idea to break it.
Let’s see an example to refresh our memory:
public class Order {
private int id;
private List<OrderItem> orderItems;
private int maxOrderItems;
public void add(OrderItem orderItem) {
boolean canAdd = canAddValidation(orderItems);
if (canAdd) {
addOrderItem(orderItem);
}
}
private boolean canAddValidation(List<OrderItem> orderItems) {
if (!CollectionUtils.isEmpty(orderItems) && orderItems.size() < maxOrderItems) {
return true;
}
return false;
}
private void addOrderItem(OrderItem orderItem) {
if (CollectionUtils.isEmpty(orderItems)) {
orderItems = new ArrayList<>();
}
orderItems.add(orderItem);
}
}
Order
contains a public method for adding an OrderItem
to it.
Before adding the item, it performs a validation.
We usually want to split big methods into smaller ones to make the code clearer, and here we’re doing exactly that by extracting some of the code into canAddValidation()
and addOrderItem()
methods.
In this case, the private methods do not stand by themselves, and only have meaning as part of the public method’s execution.
Let’s say we changed the private
methods to be public
.
Now, other objects can use these methods to perform operations that should be performed only internally by the Order
object.
For example, other objects can use addOrderItem()
to add OrderItem
to Order directly, without any validation, which may cause an invalid state of the Order
object.
But what if the private method contains complex business logic that you want to test thoroughly?
This case signals us that there is a missing abstraction in our code, and so instead of making the method public, we should extract it into a separate class.
Let’s say that in the example above canAddValidation()
method is very complex and contains many business rules.
We can introduce a new domain concept, AddItemValidator
class, and move all the logic there, which allows us to test it directly and thoroughly.
Another reason for extracting a method (any method) to a separate class, is for the sake of the DRY principle.
To summarize,
The answer to the question of how to test private methods is then: nohow!
You should only test the observable behavior of your application and not couple the tests to the implementation details.
In case the private method contains important/complex logic, extract it to a separate class and test it thoroughly.