In a lot of interviews, I came across the question: “Why package-private visibility is useful?”. The usual answer from the candidates is “you can unit test that method individually because the tests are usually in the same package as the testee”.
I just want to start with the basic idea of unit testing. You want to test a unit without it’s dependencies otherwise your test execution result will depend on the behavior of the external component. Doing this requires loosely coupled components which can be achieved in many ways, but the most popular one is using a Dependency Injection framework like Spring.
There are more about unit testing which I’m not going to write about because there are several sources about this on the internet. The focus in the post is on the testing of package-private methods.
Let’s go through an example:
public class TransferLogicImpl { @Override public void transfer(long fromId, long toId, long amount) { accountHasAmount(fromId, amount); decreaseAmountOnAccount(fromId, amount); increaseAmountOnAccount(toId, amount); } private void accountHasAmount(long fromId, long amount) { // implementation to check the account } private void decreaseAmountOnAccount(long fromId, long amount) { // decrease the amount on the specified account } private void increaseAmountOnAccount(long toId, long amount) { // increase the amount on the specified account } }
We have the class TransferLogic with one public method, called transfer. This method transfers the given amount of money from one account to another.
Let’s define a contract for the transfer method. The preconditions are:
- One should pass a valid fromId which represents the account where to transfer the money from
- One should pass a valid toId which represents the account where to transfer the money to
- One should pass a valid amount which represents the amount of money to be transferred
Valid means that the ids are existing ones and the amount is not negative or zero (sure we can define more).
The post-conditions are:
- The given amount of money should be transferred from the specified account to the other one
- If the given amount of money is not available on the source’s account, the method should throw an exception
Usually, this contract is defined on an interface level (with javadoc for example).
Let’s test this TransferLogic class. We will test the transfer method because that’s the only one which is visible and also that is the entry point. While writing the test, it might turn out that testing this method is hard because there is a big logic implemented in the private methods and also there are a lot of dependencies. One might think: “okay, let’s make the private methods package-private, then I can test them individually”.
Testing package-private methods is possible when you put the Testee and the Test class to the same package (which is almost always the case).
From contract perspective, the package-private methods usually don’t have any defined preconditions or post-conditions, this is why you have to really consider testing them.
The transfer method first checks whether the source account has enough money to transfer. If there is, the execution continues; if not, the execution fails with an exception.
After that, the method tries to decrease the amount of money on the source account which has an implicit precondition and post-condition. The precondition is that the account should have enough money and the post-condition is that after executing this method, the amount of money should be removed from the source account.
The last step (in this example at least) is to increase the money on the target account.
We have some sort of conditions for the steps but this is usually not the case. If you start testing your package-private methods, the most interesting question is: what to test?
Should I test based on the contract (if you have 🙂 )? Should I test each and every path of execution?
Usually, we don’t have any contract for package-private methods so the first testing possibility is not feasible. Let’s go with the other one.
Now we are about to test the accountHasAmount method which has two parameters; the fromId and the amount. If we want to test every execution path, we need to test it against wrong parameter values; invalid account id, negative amount, zero amount, etc.
This is also the case for the other methods as well. Additionally, what if the decreaseAmountOnAccount method tries to operate on an account which doesn’t have enough money on it? The method for this case is probably not prepared. In order to write proper unit tests for this method, we have to know the operations which are happening before the usage because then we will know the preconditions of the method. Otherwise we would test cases which cannot happen at all.
So somehow we have to ensure that the preconditions and the post-conditions are met. What can we do to do this? Separation! Most of the time, if you want to test a package-private method, you probably have too much logic in one method. If this is the case, you can extract that logic to another class where you can define pre- and post-conditions and test the class individually. What I’d suggest is to test only public methods, because there is contract for them and also they are the entry point of the whole execution chain.
Short recap at the end; if you want to test a package-private method, you don’t really have the contract for it, thus it’s really hard to properly test. For proper testing you can extract the whole method to another class where you can define the contract and test against it.
If you have any thoughts about this post, feel free to tweet me or comment & also follow me on Twitter.
Next post coming soon!
Thanks for sharing your thoughts, Arnold. Facing similar cases at work made me think what might be a situation to justify weakening methods visibility to test them. There is a case when I would tolerate this “way” 🙂
If you don’t have any mocking frameworks (Mockito, Easymock, Powermock) and public methods require building “heavy” objects, which you cannot mock, as someone, let’s say, prohibited inheritance, then testing package-private methods is something what could help to test at all. Definitely you have to analyze data flows skipped from public methods to the point where you start testing for package-private methods to avoid excessive tests with data you will never have in production.
Generally if you have enough freedom to refactor and reorganize code on your project it’s better to follow best practices and eliminate such smells, as they might be an example to follow for others who either might not be aware of classes peculiarities and limitations or just willing to ease their path through unit testing 🙂