DEV Community

Cover image for This Is Why We Don't Test Private Methods
Cesar Aguirre
Cesar Aguirre

Posted on • Originally published at canro91.github.io

This Is Why We Don't Test Private Methods

I originally posted this post on my blog a long time ago in a galaxy far, far away. It's part of an ongoing series I've been publishing, called Unit Testing 101.


Trying to test private methods causes a lot of confusion.

That's a common question we all have made when finding unit testing for the first time. These days, I found that very same question on Reddit:

Can someone explain to me why unit testing our private methods is bad?

And here's my full answer:

Because we don't want to break encapsulation.

If you have a private method, how are you going to call it from a test class or method?

It's private. You can only access it from inside the same class. That's the whole point of access modifiers: restricting access to the fields and properties that hold the internal state of a class.

And please don't make your private methods public and static to call them directly inside unit tests. They're private for a reason, right? We don't want the rest of your code to use them directly.

Exposing internals is the most common mistake when writing tests. I've seen it and fixed it before.

Let's take the HasAdmin() method from the question as an example,

private bool HasAdmin(List<string> relations, bool hasPermission)
{
    // Beep, beep, boop...
}
Enter fullscreen mode Exit fullscreen mode

Unless HasAdmin() has 0 references—if that's the case, you should remove it—another method from the same class is calling it. And you can trace the chain of method calls back to a public method.

HasAdmin(), or any other private method down in the chain of method calls, is changing something that you can observe from public methods. Probably it's affecting a return value or changing an internal state you can inspect with getters. That's what you should test instead.

To test HasAdmin(), create a User object with the right relations and permissions, call the public methods you have, and check what should change when your user is an admin or not. Maybe you return additional data only admins can access or finish an action without throwing a UnauthorizedAccessException.

You test private methods indirectly while testing the observable behavior exposed through public methods.

Et voilà!


Download your free copy of my ebook, Unit Testing 101: From Zero to Your First Tests. It covers all the basics to help you write your first unit tests in C#. Plus, get 5 bonus lessons delivered straight to your email to make your first tests even better.

Top comments (25)

Collapse
 
pengeszikra profile image
Peter Vivo

I think this is truly reflect, which is one weakest point of OOP concept.
Imho, a similar reason to avoide the OOP programming as possible.
Because even with the lightest object we are attach our functions to the data, and part of these data are protected also. So handling this data is bit painfull because always need to care about lifecycle of object not just the the data.

Collapse
 
siy profile image
Sergiy Yevtushenko

Data with operations defined on that data is not limited to OO. For example, FP monads indistinguishable from OO classes - data with operations defined on them. The only thing that actually matters is the immutability of data. As long as data immutable, one can consider private data a "context", i.e. few more input parameters to the function.

Collapse
 
pengeszikra profile image
Peter Vivo

You right. But when you attach operations to data, then you also faced to another problem, when that data is came from outer source then it need to be attach to operations, and when to send it, then need to detach from operations.

Thread Thread
 
siy profile image
Sergiy Yevtushenko

Serialization does not depend on OO or FP. There is no need to "attach" or "detach" methods either.

Collapse
 
canro91 profile image
Cesar Aguirre

That's a good point Peter. Everything lives inside an object. Thanks for your comment.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

Think of a normal modern JS/TS codebase. E.g. any Next JS, Solid, Vue... whatever.
You'll probably find near to zero OOP, however you'll sure come across functions that are not exported, that act just as a helper of some other function, their reason to exist is solely to help other function within the same file.

You can, conceptually, refer to them as private functions, equivalent to a private method for all intents and purposes of this context. You won't be testing these, either.

As a rule of thumb, if the only reason to have the keyword "export" in a function is so you can import it for testing purposes, you're doing something wrong.

Easy way to explain it is that we don't test steps (functions/methods), we test the rigour of the algorithms (input A = output B) to ensure our software keeps working as expected, wether the algorithm has 2 or 95 steps is none of our business from the testing perspective.

This explanation is just to show an example of the same situation without involving the programming paradigm to avoid getting ourselves into the weeds here. These are two completely separated discussions.

Collapse
 
pengeszikra profile image
Peter Vivo

Don't forget the WebComponent where you need to be use OOP pattern.

But what is the deep concern of private method in OOP? Maybe we would like to show just a minimal public interface to the outer word, and protect that function to called from out side. So technically enough to test the public part of our Object. Under Object development maybe we set protected method to public, so we can write a test for that. Later if our object are stable, we can move that part to protect. Or we can use Symbol key for that function which we don't want to use public, but these Symbol we share to test, but not for in our lib.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

The method of Schrödinger 😂😂 No, don't do that, it just makes things more complicated for no reason. If you test all use-cases of your public functions/methods you're implicitly testing all the private ones at the same time and if not, the coverage will tell you exactly what you are missing.

Lastly I can't honestly consider web-components as part of the "modern" ecosystem. They're a standard API, yes, but a bad one nevertheless.

Thread Thread
 
pengeszikra profile image
Peter Vivo

If you don't use framework, then WebComponent will be really handy. I created a markdown-viewer WebComponet which is part of my game development process, and that is fine. I try to skip using JS framework. Maybe check out my game development: dev.to/pengeszikra/javascript-grea...

Collapse
 
mirnes_mrkaljevic profile image
Mirnes Mrkaljevic

I think the most misleading point for developers is how they interpret the term "unit" in unit testing.
A unit is a "unit of work" and does not necessarily have to be a method in a class. Instead, a unit should be seen as a single behavior of a component towards the external world (in other words, its public interface).
Moreover, if the component adheres to the Single Responsibility Principle, writing tests is straightforward in most cases.

Collapse
 
canro91 profile image
Cesar Aguirre

Exaclty! Unit of Work != A single method

Collapse
 
siy profile image
Sergiy Yevtushenko

Determining if the component adheres to SRP is not straightforward, though. Much easier and at least as efficient is to follow the Single Level of Abstraction approach.

Collapse
 
manchicken profile image
Mike Stemle

Yeah, while I understand what you're saying, testing private methods only via their usage in other methods is poor practice IMO as it creates a paradigm where it is truly impossible to test all possible cases.

Especially in an paradigm which already encourages nearly-infinite code complexity, burying code in private methods and accepting that it will never be completely tested seems like malpractice.

Collapse
 
goodevilgenius profile image
Dan Jones

In my opinion, if you have a private method, and you can't test some part of that method from the public methods that call it, then you're not using that part of the method, and it should be removed.

There's not really a reason you wouldn't be able to test each part.

Collapse
 
canro91 profile image
Cesar Aguirre

I second this :)

Collapse
 
manchicken profile image
Mike Stemle

The part I think you’re missing is that those private methods are themselves their own function. In order to test the private methods completely, you’d need to have all possible conditions available in your public functions, and your tests would need to cover all conditions in both the public and private methods. The cyclomatic complexity can get pretty nasty pretty fast.

Another point against OOP, I guess.

Thread Thread
 
goodevilgenius profile image
Dan Jones

All possible conditions should be available from your public functions. If they're not, then those conditions are not possible, and can be removed from your function.

E.g., let's say I have something like this:

class Divider {
  public function int divide(int dividend, int divisor) {
    if (divisor == 0) {
      throw new Exception("Can't divide by zero");
    }
    return reallyDevide(dividend, divisor);
  }

  private function int reallyDevide(int dividend, int divisor) {
    try {
      return dividend/divisor;
    } catch (ArithmeticException e) {
      throw new Exception("Can't divide by zero");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the try/catch can't be tested, because divide already checks for a zero. Therefore, the try/catch is unnecessary. There is no code that calls it that can use it. So, you can leave it out, and change the second method simply to:

private function int reallyDevide(int dividend, int divisor) {
  return dividend/divisor;
}
Enter fullscreen mode Exit fullscreen mode

Now, there's no untestable code, because you got rid of the unnecessary code.

Thread Thread
 
thescottyjam profile image
theScottyJam

If you have two tests against a public method and two tests against a private one, and you change it so you instead test the private behavior through the public function, you still have a grand total of 4 tests. The quantity of tests don't have to increase because you're testing behaviors through the public API.

Also, if you really are trying to test each private function independently in order to have a low cyclomatic complexity, so you can fully test each thing, well, that doesn't really buy you anything. If you really think about it, every single operation and piece of syntax you use in the language is well tested - all you're doing is integrating those pieces together. But all of your bugs are going to lie in the integration. Similarly, if your unit of test is as small as individual private functions, well, each of those private functions will work wonderfully, but you're going to have lots of bugs in how they integrate unless you test larger chunks at once, even if they have high cyclomatic complexity.

Collapse
 
rhywynn profile image
Rhys

Not understanding the purpose of the code might be a reason to test every function. I'm not justifying it, but I could see that as a path of least resistance to ensure 'everything is tested', without the perceived overhead of designing comprehensive tests that cover every scenario.

Collapse
 
jared_williams_5785b12777 profile image
Jared Williams

If you can't reach all of your private methods from your public methods for testing, then you either have dead code, or need to reconsider the purposes of your methods.

Collapse
 
jankapunkt profile image
Jan Küster

This is why code coverage should be an essential part of unit testing. It enables to inspect those private parts and whether tests covered them, too.

Collapse
 
stevsharp profile image
Spyros Ponaris

Thanks for sharing.. I think that we test fist behaviour, not implementation details. If private methods should be test, we must consider whether they should be extracted into their own class or exposed through a public interface.

Collapse
 
joan_pearl_b1036d7ba14b48 profile image
Joan Pearl

Thank, you but no. I will test my private internal methods without breaking access rights.

You can easily in modern c# use includes to import your test units as if they were private and internal to the class being tested without breaking encapsulation.

Collapse
 
adic_threex profile image
adic threex

This is simply a problem with legacy programming languages. In modern languages ​​(like Rust) you can access private identifiers in tests without breaking encapsulation. If a language forces you to avoid testing a part of your codebase simply because it has design flaws, this is a reason to don't use it in future.

Collapse
 
htho profile image
Hauke T.

Hmm.

The way I read this article, the most prominent argument is: We don't test private methods **because* they are private.*
IMO this explanation does not give any reason at all.

For me, one reason to test private methods/functions is to make it easier to reason about them.
I want to write (and read) tests, that explain to me how a function behaves.
When a class transforms one complex state into another, I find I way too complicated to craft (and later read) the exact input states that lead to the expected output, where all edge-cases are covered. Instead I prefer to test the edge-cases at the private methods.

Whenever it's possible, I try to avoid testing private methods. But from time to time, It makes my code much easier to understand. And that is the second most important thing after writing code that does what it is supposed to do.

BTW: I wrote an article on how to test private methods in TypeScript.