Unit testing private methods through extraction

When you use unit tests you have to intentionally write code that is testable.

What about private methods? How do you test those?

And no, you should NOT change the accessibility of your methods (eg. internal, public, etc.) to make your code testable as you would lose your code’s correctness - there’s a reason why those private methods have been made private!

There are several ways to approach this problem. For example you could use the PrivateObject class. Or you could use reflection.

In this article, however, we are going to take a look at using extraction and good coding practices to make our private methods more accessible while also retaining our code’s correctness.

Let’s assume a situation where

public class Player {
    
    private void Attack() {
        System.Console.WriteLine("Enemy takes damage!");
    }
    
    private void Move() {
        System.Console.WriteLine("You moved!");
    }
    
    public override void _PhysicsProcess(float delta) {
        Attack();
        Move();
    }
    
}

You can’t test the above methods, Attack() and Move(), because they are private and not exposed to the outside world. But you also want to keep it that way, it’s an important requirement.

Making the methods public would be bad. Using reflection to test the methods would work, but it’s not fun nor a good approach in general.

Let’s use method extraction instead and turn those methods into abstractions.

public class RefactoredPlayer {
    private Attacker attacker;
    private Mover mover;

    public override void _PhysicsProcess(float delta) {
        attacker.Attack();
        mover.Move();
    }
}

What we’ve done here is we’ve refactored the Player class and extracted the Attack() and Move() methods into their own classes, Attacker and Mover. Now the RefactoredPlayer describes only the flow of the code and not it’s implementation, thus allowing us to keep the composed attacker and mover objects in the RefactoredPlayer class private while exposing the abstractions as public (see below).

public abstract class Attacker {
    public abstract void Attack();
}

public abstract class Mover {
    public abstract void Move();
}

IMPORTANT: We don’t have to test the RefactoredPlayer class anymore, we can now test the actual implementations of Attacker and Mover or the abstract classes themselves (for that you need to make an anonymous class implementation of your abstract class as you can’t instantiate an abstract class). Which one you want to do depends mostly on whether your actual implementations have extra features beyond the promises made in the abstract classes or not.

As a bonus you can now have several implementations of Attacker and Mover and depend upon abstractions instead of concrete implementations giving your RefactoredPlayer class less reasons to change and your code becomes more flexible.

public class SwordAttack : Attacker {
    public override void Attack() {
        System.Console.WriteLine("You skewer the enemy with your sword!");
    }
}

Here we have an example of an actual implementation of one of our abstract classes. We would still want to depend upon the abstraction Attacker rather than the implementation SwordAttack - to make our code more fluid and easier to change. This way we don’t have to change our RefactoredPlayer class if our implementation changes or gets swapped out to something else that is also implementing the abstract class Attacker.