How to Unit Test Private Methods in C#—And Why You Shouldn't

by Hit Subscribe 21. December 2018 04:53

When I first started unit testing, Visual Studio had a feature that automatically created private-method accessors. This was handy, because it let me unit test the private methods. Since then, I've been using tools that don't do this automatically and I don't miss it one bit. For one thing, the additional code was a mess. For another, it was just unnecessary.

Recently, I came across a post that showed how to invoke a private method. It occurred to me that you can use this technique to unit test private methods in C#. But, should you really be doing that?

I wouldn't even ask if the answer were yes. But what should you do instead? The answer's really simple, and I'll share that with you in this post. It's up to you to make the choice. However, I recommend staying away from unit testing private methods. Doing so will keep your tests more stable in the long run.

Now let's get down to it. Here's what a private method is all about in the first place.

What's a Private Method, Anyway?Don't unit test private methods in C#

A private method is a class method that's only callable from within the declaring class. If you try to access one from the outside, your code simply won't compile!

A private method looks like this:

    class ClassWithPrivateMethod
    {
        private int GetInt()
        {
            return 1;
        }
    }

It's about as simple as it gets, and you'd call it from another method in the class like this:

    class ClassWithPrivateMethod
    {
        public void DoWork()
        {
            int i = this.GetInt();
            //...do work with the int
        }
        ...
    }

You can use this.GetInt() or even simply GetInt() from within the class to call the private method.

Again, if we attempt to call it from a test (or anything outside the class), your code will fail to compile. This is what a private method does by definition. A private method is supposed to be called only from within the declaring class. You can't even call it from subclasses.

How to Call a Private Method Using Reflection

Reflection is a powerful tool we can use to peer inside the class. We can get information about a method and even invoke the method. In addition, using reflection we can call a private method on a class.

Here's an example of how to call the GetInt method on the class shown above:

// using System.Reflection;

typeof( ClassWithPrivateMethod )
    .GetMethod(
        "GetInt", 
        BindingFlags.NonPublic | BindingFlags.Instance )
    .Invoke(
        new ClassWithPrivateMethod(), 
        null );

As you can see, we'll need to get the type. Using the type we can get the MethodInfo as long as we know the method name. MethodInfo lets us invoke the method on a class instance. It takes parameters, but here we're passing null since GetInt doesn't have any parameters. We can put this into a reusable helper method. Then, we can use that helper method in our unit tests to test private methods.

How to Make It Reusable for Testing

We'll need to input a few things like the method name, the type, an instance, and the parameters. In the GetIntExposed method, I've passed null to Invoke for the parameters. But, we need to be able to pass parameters. That's pretty easy to handle. Let's take this step by step.

First, since we're passing both the type and an instance of the target class, we can use a generic like this:

static internal TReturn GetIntExposed<TInstance, TReturn>(TInstance instance)
{
    Type type = instance.GetType();
    BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
    MethodInfo method = type.GetMethod("GetInt", bindingAttr);
    
    return (TReturn)method.Invoke(instance, null);
}

The initial step in this refactoring is to make the method generic so you can use different classes and return types. I've also broken out each variable so you can see them in more detail.

The steps for using reflection to invoke a private method are as follows:

  1. Get the type of the instance.
  2. Set the binding flags to get the private instance method info.
  3. Invoke the method on the class and cast the returned value to the specified return type.

Of course, we'll want to be able to invoke methods named something besides GetInt. The next refactoring will let us pass in the method name and its parameters. Here's what it'll look like:

static internal TReturn CallPrivateMethod<TInstance, TReturn>(
    TInstance instance,
    string methodName,
    object[] parameters)
{
    Type type = instance.GetType();
    BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
    MethodInfo method = type.GetMethod(methodName, bindingAttr);
    
    return (TReturn)method.Invoke(instance, parameters);
}

And now we have a reusable method to call private methods from unit tests. But will it always work? I really don't know since I never test private methods. See, private methods are implementation details, and you shouldn't be testing those. Let me explain....

Why Shouldn't You Test Private Methods?

First of all, as already stated, private methods are private. They aren't meant to be invoked outside the declaring class. But let's just say, for argument's sake, that that's a semantic issue that doesn't apply to unit testing. Even if that were the case, you still shouldn't unit test private methods. See, the problem with unit testing this way is that your unit tests will be fragile. They'll depend too much on implementation details that can and will change.

Let's say you're just starting to build out a class to handle some business logic. The details are still a bit fuzzy, and as you know things tend to take shape only after you've started implementation. Further confounding the issue, you'll write multiple tests for one unit. So, you've got this private method and you know it's likely to change. In fact, if you're keeping your code clean and applying good OOP, it will definitely change. You'll be refactoring!

You may find some reuse for a private method or decide to refactor the class to make it simpler. When you cover those private methods with a bunch of tests, you'll have a lot more code to change simply because the implementation details change. Besides, unit testing this way is missing the point of unit testing!

What Should You Do Instead?

Only test the interfaces. That's what the consumers of your code care about.

See, unit testing is about making sure your code does what's expected. In the best cases, the expectations are defined up front. Theoretically, you really only have to test that your code follows the requirements. This is the foundation of behavior-driven development or BDD. In reality, rarely are the requirements spelled out in a way that covers enough cases to make your code "bulletproof." Besides, you often will break your modules into smaller pieces and reuse many classes and methods. Those public reusable methods are the interfaces you'll want to test.

So what about the logic that's hidden inside the private methods? You definitely need to test it! But, you don't do so by calling them directly. Instead, you should write your tests so all branches are covered. This is how you test your class to make sure the implementation details are correct.

There are so many ways to implement the class. You might find a better algorithm to use to speed things up or use less memory. However, you don't really care about those things when you're unit testing. You could have your tests measure overall speed or memory, but there are too many variables to make this accurate. Benchmarking is more appropriate for that purpose anyway.

Basically, the tests should see your class as a black box. This isn't to say that whoever is writing the test shouldn't know what's inside. On the contrary! You may need to write your tests in such a way that they cover cases for threading, asynchronous, and parallel processing. Those are difficult to cover, and you don't want to go in blind when you're testing that kind of code. But for the most part, you should write tests from a consumer perspective—even if you're the only consumer.

Take It From Here

Private methods are part of the details/internals about the class—the innards. Unit testing should be testing the unit that's accessible rather than the internals. In other words, it should only be concerned with testing the interface to the class—that is, the public methods. The need to test the innards of a class directly is a combination of two code smells: "Inappropriate Intimacy" and "Indecent Exposure." It begs for a new smell: "Peeping Test." Much like a "Peeping Tom," this unit test pries its way into the private parts of the SUT (subject under test). Don't create Peeping Tests!

This post was written by Phil Vuollet. Phil uses software to automate processes to improve efficiency and repeatability. He writes about topics relevant to technology and business, occasionally gives talks on the same topics, and is a family man who enjoys playing soccer and board games with his children.

Tags:

Blog