Testing protected methods

June 24th 2022 Unit Testing .NET

In most cases, you do not want to write tests for non-public methods, because that would make the tests dependent on the internal implementation of a class. But there are always exceptions.

For example, there might be a private method that implements important business logic used internally by the class. If such a method is largely independent of the state and dependencies of the class, or even pure, then it might be a good candidate for refactoring into a separate class. Before doing this, you should cover the existing method with some tests that serve as a safety net for your code changes.

Let us start with a simplified example of a class with such a non-public method, which we can then use to look at some different testing approaches:

public class Service
{
    private readonly IDependency dependency;

    public Service(IDependency dependency)
    {
        this.dependency = dependency;
    }

    public void WrapperMethod()
    {
        var original = this.dependency.GetValue();
        var processed = this.ImportantMethod(original);
        this.dependency.SendValue(processed);
    }

    protected string ImportantMethod(string input)
    {
        return input;
    }
}

The key point of the above code is that the method in question can only be called indirectly from the outside via another method. Also, the inputs and outputs of the method cannot be accessed directly. The method being tested in this example is protected. If it were private, one of the suggested approaches would not work. However, if you convert a private method to a protected one, the encapsulation is not broken and, more importantly, the existing behavior is not changed.

One way to test this method would be to call a public method that indirectly calls the method we want to test. We just need to find a way to access and control the inputs and outputs of the method. In this case, we can do this by mocking the methods of a class dependency:

[Test]
[TestCase("a", "a")]
public void TestThroughInteraction(string input, string expectedOutput)
{
    var mocker = new AutoMocker();
    var service = mocker.CreateInstance<Service>();

    var dependencyMock = mocker.GetMock<IDependency>();
    dependencyMock.Setup(s => s.GetValue()).Returns(input);
    dependencyMock.Setup(s => s.SendValue(
        It.Is<string>(output => output == expectedOutput)));

    service.WrapperMethod();

    mocker.VerifyAll();
}

This works, but it's not entirely clear what the test is doing. We mock one method of the dependency to provide the input to our method, and another to verify the output. And keep in mind that the class under test is simplified. In a real scenario, there could be multiple dependencies involved, and the values could be processed further in the class before being passed to our method under test. All of this would make the test even more complicated or potentially even impossible to write this way.

To avoid having to call the method indirectly, you can derive a class from your class under test and expose the method under test through a public method:

private class DerivedService : Service
{
    public DerivedService(IDependency dependency)
        : base(dependency)
    {
    }

    public string ImportantMethodWrapper(string input)
    {
        return base.ImportantMethod(input);
    }
}

This simplifies the test and makes it much more understandable:

[Test]
[TestCase("a", "a")]
public void TestUsingDerivedClass(string input, string expectedOutput)
{
    var mocker = new AutoMocker();
    var service = mocker.CreateInstance<DerivedService>();

    var result = service.ImportantMethodWrapper(input);
    result.Should().Be(expectedOutput);
}

If you do not want to write a derived class just for testing, you can use reflection instead. Just use a library like ReflectionMagic to simplify the syntax:

[Test]
[TestCase("a", "a")]
public void TestUsingReflection(string input, string expectedOutput)
{
    var mocker = new AutoMocker();
    var service = mocker.CreateInstance<Service>();

    string result = service.AsDynamic().ImportantMethod(input);
    result.Should().Be(expectedOutput);
}

All of the above tests are data-driven, so several different input/output pairs can be tested with a single test. Of course, this would not work if inputs and outputs are not simple types that can be used in an attribute. In a previous blog post, I discussed some alternatives for such cases.

A working example project with the above tests is available in my GitHub repository.

Although you should not normally test non-public methods, there are ways to do so if you really want/need to. In this post I have shown three possible approaches:

  • by calling the non-public method indirectly,
  • by exposing the non-public method as public from a derived class,
  • and by invoking the non-public method via reflection.

If you do this, be sure to consider refactoring the code to make this method public, for example, by moving it to a separate class that can be injected as a dependency into the class where the method is currently placed.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License