Should I Test Private Methods? Understand The Why

No matter what programming language you use, writing unit tests can be challenging. This is especially true for those who are not used to writing unit tests in their code. One of the most common problems developers face is whether they should or should not write unit tests to private methods. Hopefully, I can help you answer this dilemma.

First of all, making the choice of asking this kind of question is what differentiates average developers from good developers. Although this doesn’t mean you are a good developer yet, unless you already are one, it shows your ambition to improve your coding skills which will allow you to write code that matters, rather than filling up files with more lines of code. The latter is a common scenario seen among developers who are yet to improve their skillset in the art of writing tests.

The practice of unit testing is the practice of simulating processes between a public interface and the client. This allows them to test whether software systems return an expected outcome to the client. Having said that, you should not write test cases to test private methods as it defeats the purpose of testing real-world scenarios between the public interface and the client, but you can test private methods by writing test cases that interact with the public interface.

Some of you will agree with not writing unit tests to the private methods. Some will still feel tempted to do so. If you are the last guy, I recommend you not close this article before looking for other articles saying what you want to hear: “You should test private methods“. I’ll explain by asking you a series of questions:

Why Do You Want To Test Private Methods?

After understanding the reason why unit tests exist, you shouldn’t feel the need to write test cases to private methods as it is not a realistic scenario that could happen.

Most programming languages don’t allow you to access private methods to test them, although there are exceptions to the rule such as TypeScript. That in itself is a hint given to developers to not test private methods. However, if you haven’t caught this hint, allow me to explain with a non-programming-related scenario.

Imagine you buy a house alarm and decide to install it in your home. After installing it, you feel much better about increasing the security of your home. However, you feel the need to test that the house alarm is making a loud noise when someone breaks into your house and calls the police for immediate assistance. Therefore, you have pretended to act as the “thief” after you leave your home and activate the alarm system while leaving the windows unlocked for you to “break” into your own house.

Once you break into your own house, you immediately hear the noisy house alarm (and if you enabled the functionality to automatically call the police, be ready to demonstrate that you are the homeowner). This test is a success.

For some reason, you want to test that the alarm system has batteries. This, in theory, could be understood as a valid point, as you need batteries to make the system work. However, is this testing a real-world scenario? Will you care if the alarm system has batteries or not? or Will you care if the alarm system will deter thieves from staying too long at your house? Probably the second option, right?

Testing whether the alarm system has batteries might be unnecessary as the first test of you simulating “breaking” into your house covered the scenario of whether the system has batteries, as without it it will never make a loud noise and call the police right away.

We can take this example into the world of programming. Private methods can be part of and could be the most important aspect of a public interface process. However, it is the outcome that matters the most and not the internal processes that get executed to generate that outcome.

Another point to make in our house alarm example is: are you going to place the alarm system outside of your house to test whether it has batteries or not? Certainly not. Otherwise, I really don’t know why you spent money on an alarm system. If we do so, anyone could walk by the house and check if the system has batteries. Also, they could check the make and model of the alarm system to find ways to hack the system.

In the same way, if we decide to expose our private methods, it dismisses the purpose of why they were designed as private in the first place. They are supposed to not be accessible by any outsiders. They are supposed to be used by internal processes only, or would you want to expose the internal secrets of your system and open room for your code to be exploited?

Is Unit Testing Private Methods Considered A Bad Practice?

This is a topic that creates controversy among developers, but before answering this question for you, you need to answer it yourself. There are concepts in software development that in theory make sense, but in practice, it can be tricky. Although I would like to give you the exact answer, there’s no research or evidence that will support my thoughts, nor any of the many opinions you will find on the web with regards to this topic. This is my thought.

Testing private methods is considered by many developers a bad practice, but that doesn’t mean it is bad practice. That’s what many people believe. It turns into a bad practice because you need to make your method public to directly test it. This is considered bad practice as developers adjust their code to give higher preference to using the testing tools incapable of accessing private methods, rather than the initially given logic.

Reasons You Are Tempted To Write Unit Tests to Private Methods

Even if you are a firm believer in not writing tests to private methods, you still find yourself writing these kinds of tests. It is important to understand that even when there are principles in the industry, there are situations where it makes sense to not follow the principles. The development of software is different for any project, and not every project has the same requirements, goals, budget, client, etc.

Therefore, the most important aspect is to make the most reasonable decision based on the circumstances you face. It is just like life; every day, hour, minute, and even every second we make decisions. For example, you are making the decision to read this paragraph as it is relevant for you, or rather skim through it and get the main objective of this blog. Some of these reasons you will relate, as at some point I had to write private test cases whether I was brand new to the world of testing code or due to the nature of the business.

Increase Code Coverage

Based on the project, the CI/CD configuration includes the coverage checks. If you are not sure what I refer to by code coverage, this means the test cases written cover certain lines and/or conditions of your code. For example, I have a simple function called getEmailAddress:

/**
 * gets the email address based on the name option provided
 * @param {'james' | 'falcao'} name 
 */
getEmailAddress(name) {
  if (name === 'james') {
     return '[email protected]';
  } else if (name === 'falcao') {
     return '[email protected]';
  }

  return '';
}

If I decide to execute a test case to verify I get the correct name for the name of Falcao, I will need to have the following test:

it('should get falcao\'s email address', () => {
   const emailAddress = getEmailAddress('falcao');
   expect(emailAddress).to.be.eql('[email protected]');
}) 

Our code coverage for this piece of code will probably be around 33%, Why? Because code coverage will take into account only the lines of code of the getEmailAddress function that were executed during the unit tests.

Now, imagine projects’ CI/CD requirement where you have to have about 60% ~ 80% code coverage, and a large portion of your written code was private methods. It tempts you to write unit tests for those private methods, doesn’t it?

In theory, you shouldn’t need to write these kinds of tests as if public methods are invoking private methods, lines of code of these private methods most likely are included within the code coverage. However, there could be exceptions to this case. Sometimes, the logic of the private methods is complex enough that they deserve a dedicated set of unit tests, as public methods don’t fully cover all the conditional cases of the private methods.

Every Method Needs Its Own Test Case

First and foremost, this is an assumption that inexperienced developers practicing the art of writing code tests make, especially during the first few times of writing unit tests. I can validate that as I was once in that position. I was new to writing unit tests, and I related the part of unit as testing pieces of code independently, which led me to make the mistake of writing test cases for each method I write in code.

With more practice and a clearer understanding of the reason why unit test cases exist, I improved my unit test skills by not necessarily writing a test for every single piece of code I wrote, but instead a test case for those public methods that activate multiples of methods within the code.

There Is A Lot Of Logic In Private Methods That Needs Special Attention

Let’s present this scenario: You finished the development of a feature and you ended up with a lot of logic inside private methods. Now, it is time to write test cases, but it becomes complex to write tests through public methods that will use all the logic inside private methods, even if you write good test cases. You consider there should be a dedicated test for the different logic running inside the private methods.

However, if there is tons of logic inside a method, regardless of whether it is private or public, that means you are trying to do too much in one method. Methods and/or functions should be limited to the execution of one process. Adding additional logic makes the process more complex, which makes your test cases more complex. Take this as an opportunity to refactor your code instead.

The problem might not be that you want to implement specific tests for your private method, but the problem could be that your code lacks simplicity. Complex code is the equivalent of complex test cases. If the complex code is in private methods, this could increase the complexity of writing good tests, even if you find a way to directly test private methods.

What Do You Think?

This is a controversial topic, and there’s plenty of room to justify one position over the other. If you have read several articles on the topic of testing private methods, you will see a constant clash of thoughts. I’m interested to hear your thoughts with regards to this and your reasons why you have chosen one position over the other.