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
.