Introduction

In this article I will go through some untestable classes and make them testable without third party dependencies. The best way of doing this is using delegates that we can provide in the class’s constructor and then configure them while testing. Let’s find out how to do that but first…

What is untestable code?

There are some things that makes our code untestable such as:

  • Not programming to interfaces
  • Newing up objects instead of using IOC
  • Not using dependency injection

As programmers we can take some steps to reduce or even eliminate these scenarios. Unfortunately, this is not always possible. For example, what if the framework itself does not follow these rules? I am looking at you System.IO

Examples of untestable code

Imagine the following code:

public class FileContentWordCounter
{
    public async Task<int> CountAsync(string file)
    {
        string fileContent = await File.ReadAllTextAsync(file);
        int count = fileContent.Split(" ").Length;

        return count;
    }
}

Here we have a FileContentWordCounter which as the name suggests asynchronously counts the words in a file. This is one of the examples of an untestable code because .NET does not have interfaces that encapsulates the file system yet.

Here is an example of how a client could use this code:

class Program
{
    static async Task Main(string[] args)
    {
        var fileContentWordCounter = new FileContentWordCounter();
        int count = await fileContentWordCounter.CountAsync("C://mockFile.txt");

        Console.WriteLine(count);
    }
}

How to Test the Untestable

What can we do to make this code testable? The easiest and cleanest way in my opinion is to just use a delegate that wraps the untestable code:

public class FileContentWordCounter
{
    private readonly Func<string, Task<string>> _readAllTextAsync;

    public FileContentWordCounter(Func<string, Task<string>> readAllTextAsync)
    {
        _readAllTextAsync = readAllTextAsync;
    }

    public async Task<int> CountAsync(string file)
    {
        string fileContent = await _readAllTextAsync(file);
        int count = fileContent.Split(" ").Length;

        return count;
    }
}

Here we replaced the side effect causing static class with a Func that returns Task so we can still be able to await it and use it asynchronously.

Wait a minute! Now the client cannot call this code without providing an instance of a Func in order to use the file system and he is not happy. Let’s fix this really quick:

public class FileContentWordCounter
{
    private readonly Func<string, Task<string>> _readAllTextAsync;

    public FileContentWordCounter() :
        this(async file => await File.ReadAllTextAsync(file))
    {
    }

    internal FileContentWordCounter(Func<string, Task<string>> readAllTextAsync)
    {
        _readAllTextAsync = readAllTextAsync;
    }

    public async Task<int> CountAsync(string file)
    {
        string fileContent = await _readAllTextAsync(file);
        int count = fileContent.Split(" ").Length;

        return count;
    }
}

Here we are using a parameter less public constructor which calls an internal constructor using a default func which is defined from us. This class is also dependency injection friendly and uses inversion of control. Also, the client can use the class as he was using it before the refactoring so we did not break the public api.

Wait a minute again! Nobody wants to write unit tests in the same assembly as the main logic. Isn’t internal means that only inside of the assembly this code is accessible? The answer is yes…but not always. Unit tests can access the internal resources of an assembly. Let’s create a unit test assembly and see how to do that.

Here is how my solution looks like:

Vs_Initial_Screen

If we attempt to instantiate FileContentWordCounter class we will quickly come across to this:

Vs_Initial_Screen

Here we can clearly see that we do not have access to the internal constructor. Fortunately for us this is a very easy fix. Just go to FileContentWordCounter.cs and add to the top of the class:

[assembly: InternalsVisibleTo("Unit_Tests_Namespace_Goes_Here")]

Our example now looks like this:

[assembly: InternalsVisibleTo("UnitTestDemo.Tests")]
namespace UnitTestDemo
{
    public class FileContentWordCounter
    {
        private readonly Func<string, Task<string>> _readAllTextAsync;

        public FileContentWordCounter() :
            this(async file => await File.ReadAllTextAsync(file))
        {
        }

        internal FileContentWordCounter(Func<string, Task<string>> readAllTextAsync)
        {
            _readAllTextAsync = readAllTextAsync;
        }

        public async Task<int> CountAsync(string file)
        {
            string fileContent = await _readAllTextAsync(file);
            int count = fileContent.Split(" ").Length;

            return count;
        }
    }
}

Now if we go to the unit test project’s FileContentWordCounterTests.cs class we have 2 constructors available:

Vs_Initial_Screen

Now all we must do is to…

Write unit test with the help of Delegates

public class FileContentWordCounterTests
{
    [Fact]
    public async Task CountAsyncTest()
    {
        Func<string, Task<String>> readAllTextAsyncMock = file =>
        {
            return Task.FromResult("This is mock file content");
        };

        FileContentWordCounter fileContentWordCounter = new FileContentWordCounter(readAllTextAsyncMock);
        int actual = await fileContentWordCounter.CountAsync("mock file path");
        int expected = 5;

        Assert.Equal(expected, actual);
    }
}

Here we can provide a mock version of file system access delegate and successfully unit test our initially untestable code. We did not use any external resources for our examples although there are many.

What is your favorite way of unit testing untestable code? Share with me in the comments or via twitter.

Become a Subscriber

Subscribe to my blog and get the latest posts straight to your inbox.