Testing Internal Methods

Testing internal methods of a class

Testing Internal Methods

posted in dotnet on  •   • 

Typically you want to write tests only for public methods. You want to avoid writing tests for private methods so that when you change the implementation, your tests remain green and you can do that ruthless refactoring without having to worry about introducing new bugs.

The tests are your safety net you want to rely on!

When

There are actually many cases where you want to do this:

Legacy Singletons

The Legacy Code Dilemma
When we have to change code, we should have tests in place.
To put tests in place, we often have to change code.

Your old legacy codebase has something like:
(yeah, it’s probably a lot more convoluted than this)

public class Singleton
{
  public static readonly Singleton Instance = new();
  private Singleton() { }
}

Refactored Testable Singleton

We introduce an interface, mimicking the original API and provide an internal setter so that we can substitute it with something else during testing!

public class Singleton : ISingleton
{
  private static ISingleton _instance = new Singleton();

  public static ISingleton Instance
  {
    get => _instance;
    // We introduced a "seam"!
    internal set => _instance = value;
  }

  private Singleton() {}
}

Limiting the API surface

Inside project X you have some helper classes/functions, a few Extension Methods, or maybe part of your startup code has some logic in it.

These are all internal because you like your projects to have a clean public API, exposing only those things that external users are interested in, and hide everything else.

You don’t want to make them public, but obviously you do want to write tests for them!

When Not

public class Calculator
{
  public Result Calculate()
  {
    var step1 = Step1();
    var step2 = Step2(step1);
    return Step3(step2);
  }

  internal object Step1() {}
  internal object Step2(object data) {}
  internal Result Step3(object data) {}
}

I’ve seen code like this. Instead of testing the public Calculate method only, there are also UnitTests for the sub-algorithms, methods which have been made internal only for those UnitTests.
Maybe even mock out the Step1-3 when testing the public method 😲

It’s almost always a bad idea; as your implementation and your tests are now very strongly tied together. It’s become hard to change the sub-parts. Worse, if the requirements change, Step1-3 might not make much sense anymore but you’re probably going to endure anyway rather than rewrite all those tests…

How

This blog post was supposed to be just this but, well, I got carried away…

Old Legacy Project Format

Create an AssemblyInfo.cs class and add:

using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Company.Project.UnitTests")]

New SDK-Style Project Format

Add to the csproj file:

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
    <_Parameter1>Company.Project.UnitTests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

Summary

I could easily remember the AssemblyInfo version but the csproj one… not so much.

Ready for copy-pasta now!

Somehow I ended up writing about “high cohesion”, “Working Effectively with Legacy Code” and “encapsulation” instead!


Stuff that came into being during the making of this post
Tags: testing