Solve Unit Testing challenges in C#

Jayadev NairSr.Software Engineer
Published:
Hello there! As a developer I have modified and refactored the unit tests which was written by fellow developers in the past. On the course, I have gone through various misconceptions and technical challenges when it comes to implementation. I would attempt to address those here.

To my convenience I have used certain tools and frameworks throughout the examples in this article and I assume the reader have knowledge on C# language, some experience writing Unit tests, any Mocking framework, and any NUnit test framework. 


What is being tested in a unit test?

A unit test is always an independent method that invokes a target method (code under test) and ensures it doesn’t breaks and gives expected results in every possible situation. A simple unit test method (test scenario) would invoke the target method providing multiple inputs to it (test cases) and succeeds when expected result is delivered against each of those test cases (See Exhibit #1). Does that define a whole unit test? The answer is No. 


Exhibit #1

Code under Test

public decimal Multiply(decimal number1, decimal number2)
{
    return number1 * number2;
}

Unit Test

[Test]
public void Multiply_CanMultiplyDecimals([Values(0,234.5,2334890.234)]decimal number1, 
                                        [Values(2342.3214, -123123.2, 0)]decimal number2)
{
    //Arrange
    var program = new Calculator();

    //Act
    var product = program.Multiply(number1, number2);

    //Assert
    Assert.AreEqual(number1*number2, product);
}

We developers understand that every piece of code we write has dependencies to other units of code and it is as such consumed by other units. And we are bothered that any future changes to those, should not break the piece of code we are writing. Hence output versus input is not the only aspect what matters, but shielding every line of code inside a method is also important. Thus by protecting my lines of code, I ensure any unintended changes made to the code fails my unit tests and warn the future developer to either rollback the changes or change unit tests to comply his changes. This is very important while writing unit tests and a basic unit test as we discussed in exhibit #1 might not help on this when we start writing tests for more complex functions than that of a basic multiplier.


How to ensure I am writing code as testable units?


Here are few thumb rules while writing a unit of code -


AccessibleFunction/Method you write is accessible from the test class

A better way of keeping test classes is to include all of them into another project as such, suffixing name with Tests or UnitTests (e.g. MyProject.Tests.csproj). Hence, to access the target methods from this external library, method modifier should either be public or internal (with InternalsVisibleTo attribute applied to assembly)


SimplifiedAvoid unnecessary private method calls, keep it simple and readable

As per Single Responsibility principle, each classes should be written to achieve a single functionality and each method within which should perform a single operation towards the goal. Try to avoid writing multiple methods in the same class to achieve a functionality and invoke one from the other unless you really need them to. If you got a reusable piece of code, identify its behavior, and consider refactor that to another class like a Utility class. This will help in keeping the code cleaner and readable too.


IsolatedCode can be isolated from other implementations during test invocations

To achieve the isolation for any piece of code, we should ensure the entities depends on abstractions and not concretions (as stated on Dependency inversion principle). All entities we consume within our piece of code should have an abstraction and declarations are made as read only private members. This helps to ensure them not being modified other than at constructor where we could inject our instance into. Use a default constructor to chain the other constructor to inject original concretions, thus application can continue using the default constructor and test methods could use overloaded constructor (see Exhibit #2). An alternative would be property injections using any of the IoC container (per the project standards).


Exhibit #2

public class Repository
{
    private readonly IConnectionManager _connectionManager;
    private readonly IConfigurationManagerFacade _configurationManagerFacade;
    private readonly ILogger _logger;
    // Unit test uses this ctor()
    public Repository(IConnectionManager connectionManager, 
                    IConfigurationManagerFacade configurationManagerFacade, ILogger logger)
    {
        _connectionManager = connectionManager;
        _configurationManagerFacade = configurationManagerFacade;
        _logger = logger;
    }


    // application uses this ctor()
    public Repository() : this(new ConnectionManager(), new ConfigurationManagerFacade(),
                                new Logger())
    {
    }
}

When it comes to isolation of the code, there are few challenges developers might face. For instance, when we use static method calls as on Exhibit #3 where developer cannot rely on an abstraction and obviously no concretion as well.

 

Exhibit #3

var connectionString = ConfigurationManager.ConnectionStrings[connectionName]
.ConnectionString;

In these cases, best bet is to make use of a Facade pattern. See Exhibit #4 how problem discussed in Exhibit #3 has been solved by wrapping a static function call into a concretion that has an abstraction. This abstraction can be used in the target code to invoke the method and thus keep our thumb rule up.

Exhibit #4

public interface IConfigurationManagerFacade
{
    string GetConnectionString(string connectionName);
}


public class ConfigurationManagerFacade : IConfigurationManagerFacade
{
    public string GetConnectionString(string connectionName)
    {
        return ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
    }
}

Usage in Exhibit #3 becomes -

var connectionString = _configurationManagerFacade.GetConnectionString(ConnectionName);

See Exhibit #2 that depicts declaration and constructor injection of ConfigurationManagerFacade. This same approach shall be chosen for extension methods and to those types which developer do not own and does not have abstractions. We commonly face this issue while working with third party libraries and few .NET types like a StreamReader Class (System.IO). I have shown a pattern below that would make things a little easier (see Exhibit #5).

Exhibit #5

public abstract class NonStaticFacade<T> where T : class
{
    protected readonly T OriginalInstance;

    /// <summary>
    /// For facades that wraps a non static object, might require a null check on the 
    /// wrapped subject
    /// </summary>
    /// <returns>returns true if the wrapped object is null</returns>
    public bool IsNull()
    {
        return OriginalInstance == null;
    }

    /// <summary>
    /// Facade placeholder to return the original wrapped subject
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T Get()
    {
        return OriginalInstance;
    }

    protected NonStaticFacade(T originalInstance)
    {
        this.OriginalInstance = originalInstance;
    }

    protected NonStaticFacade()
    {
    }
}
public class StreamReaderFacade : NonStaticFacade<StreamReader>, IStreamReaderFacade
{
    public StreamReaderFacade(StreamReader originalInstance) : base(originalInstance)
    {
    }

    public string ReadToEnd()
    {
        return OriginalInstance.ReadToEnd();
    }
}

Thus, by wrapping the original instance inside a façade helps to access the entities through an abstraction. And to instantiate these facades, rather than injecting non static Facades through constructor we could use a static façade that do the job as shown in Exhibit #6

Exhibit #6

public interface IStreamReaderStaticFacade
{
    IStreamReaderFacade Create(IStreamFacade receiveStream, Encoding encoding);
}


public class StreamReaderStaticFacade : IStreamReaderStaticFacade
{
    public IStreamReaderFacade Create(IStreamFacade receiveStream, Encoding encoding) 
    {
        return new StreamReaderFacade(new StreamReader(receiveStream.Get(), encoding));
    }
}

Usage (see highlighted declarations)

public class SampleOnNonStaticFacade
{
    private readonly IStreamReaderStaticFacade _streamReaderStaticFacade;
    private readonly IWebRequestStaticFacade _webRequestStaticFacade;


    public SampleOnNonStaticFacade(IStreamReaderStaticFacade streamReaderStaticFacade, 
    IWebRequestStaticFacade webRequestStaticFacade)
    {
        _streamReaderStaticFacade = streamReaderStaticFacade;
        _webRequestStaticFacade = webRequestStaticFacade;
    }


    public string ReadFromStream(string url)
    {
        var webRequestFacade = _webRequestStaticFacade.Create(url);
        var authorization = "Basic " + 
            Convert.ToBase64String(Encoding.UTF8.GetBytes("username:password"));


        webRequestFacade.AddHeaders("Authorization", authorization);
        webRequestFacade.ContentType = "application/json";
        webRequestFacade.Method = "POST";


        var webResponse = webRequestFacade.GetResponse();
        using (var receiveStream = webResponse.GetResponseStream())
        {
            var streamReaderFacade = _streamReaderStaticFacade.Create(receiveStream, 
                                Encoding.UTF8);
            var responseString = streamReaderFacade.ReadToEnd();
            return responseString;
        }
    }
}

The discussed pattern in Exhibit #6 becomes even challenging when we require original reference of the instance such as accessing a Context key value pair that keeps changing in the memory between our statements. I have faced this while working with OWIN collections in a Web API 2, the above pattern let us access the copy of an original instance and we need the reference of original instance as such. In these situations, a Lazy façade could help where instead of injecting original instance in the constructor we pass a Func<OriginalInstance> (See Exhibit #7).

Exhibit #7

public abstract class NonStaticFacadeLazy<T> where T : class
{
    protected readonly Func<T> OriginalInstanceFunc;


    public virtual bool IsNull()
    {
        return OriginalInstanceFunc.Invoke() == null;
    }

    public virtual T Get()
    {
        return OriginalInstanceFunc.Invoke();
    }

    protected NonStaticFacadeLazy(Func<T> originalInstanceFunc)
    {
        this.OriginalInstanceFunc = originalInstanceFunc;
    }

}
public class AppContextFacade : NonStaticFacadeLazy<AppContext>, IAppContextFacade 
{
    public AppContextFacade(Func<AppContext> originalInstance) : base(originalInstance)
    {
    }

    public ITransactionFacade CurrentTransaction => new 
            TransactionFacade(OriginalInstanceFunc.Invoke().CurrentTransaction); 
}

How to write a Unit test?

The goal being the protection of original application code, we should ensure every line of written code is executed in the way I wrote the code, assignments are happening as expected and finally results are being passed on to right hands. For this example lets us choose Repository class we discussed in Exhibit #2 as the code under test. See the Retrieve() function in Exhibit #8 which is the method under test.

Exhibit #8

public class Repository
{
    private readonly IConnectionManager _connectionManager;
    private readonly IConfigurationManagerFacade _configurationManagerFacade;
    private readonly ILogger _logger;

    private const string ConnectionName = "defaultConnection";

    // Unit test uses this ctor()
    public Repository(IConnectionManager connectionManager, 
                    IConfigurationManagerFacade configurationManagerFacade, ILogger logger)
    {
        _connectionManager = connectionManager;
        _configurationManagerFacade = configurationManagerFacade;
        _logger = logger;
    }

    // application uses this ctor()
    public Repository() : this(new ConnectionManager(), 
                            new ConfigurationManagerFacade(), new Logger())  
    {
    }

    public bool Retrieve(string storeProcedureName, Func<IDataReader, bool> readFunc)
    {
        var connectionString = 
                _configurationManagerFacade.GetConnectionString(ConnectionName);
        var connection = _connectionManager.CreateConnection(connectionString);
        var command = _connectionManager.CreateCommand(storeProcedureName);
        command.Connection = connection;

        connection.Open();
        bool success;
        using (var reader = command.ExecuteReader())
        {
            success = readFunc.Invoke(reader);
        }
        connection.Close();

        if (!success)
        {
            _logger.Trace("Failed to read data from {0}", storeProcedureName);
        }

        return success;
    }
}

As the first step of writing the unit test, let us mock the members of target entity that helps to monitor the actions attempted on them within the code under test (see Exhibit #9). I am using NSubstitute as the Mock generator in the below example.

Exhibit #9

[Test]
public void Retrieve_CanRetrieve()
{
    //Arrange
    var connectionManager = Substitute.For<IConnectionManager>();
    var configurationManagerFacade = Substitute.For<IConfigurationManagerFacade>();
    var logger = Substitute.For<ILogger>();
}

Since we are injecting mock objects to the target entity, we can easily control the target code with more of our mock objects that are expected in each line of code. For instance, see the connection manager instance is supposed to create and return an IDbConnection instance on second line of target method, let us ask the connection manager mock to return our own mock connection there. Thus, let us add set of behaviors our primary mock objects should perform on each statements within the target method as shown below (Exhibit #10).

Exhibit #10

// First statement when connectionstring is attempted to retrieve
configurationManagerFacade.GetConnectionString("defaultConnection").Returns(connectionString);
// second statement to create a connection
var mockConnection = Substitute.For<IDbConnection>();
connectionManager.CreateConnection(connectionString).Returns(mockConnection);
// third statement to create a command
var mockCommand = Substitute.For<IDbCommand>();
connectionManager.CreateCommand(storedProcedureName).Returns(mockCommand);
// seventh statement when execute reader is invoked
var reader = Substitute.For<IDataReader>();
mockCommand.ExecuteReader().Returns(reader);

Now invoke our target method from the test method and pass input parameters for the function. Here, since one of the argument is a function that is supposed to get the data reader during execution, get that on a variable which in turn can be asserted to ensure that expectation is met.

Exhibit #11

//Act
IDataReader dataReader = null;
var storedProcedureName = "TestStoredProcedure";
var result = repository.Retrieve(storedProcedureName, (dr) => 
{
    dataReader = dr;
    return true;
});

Once we acted upon the target method, it is the time we should see all our expectations were met by asserting against our mock objects. Assertions are not limited to output values, but method invocations we expect on each mock objects and even the order of its executions can be asserted. A very strict assertion helps to keep our code strictly protected from unintended changes in future (see Exhibit #12).

Exhibit #12

// below calls are received and received in order of their appearance
Received.InOrder(() =>
{
    // the supplied connection is assigned to command object
    mockCommand.Connection = mockConnection;
    // a connection open was invoked
    mockConnection.Open();
    // reader was created
    mockCommand.ExecuteReader();
    // connection was closed after reader is created
    mockConnection.Close();
});
// trace was not written since success=true
logger.DidNotReceive().Trace(Arg.Any<string>());
// reader returned from command is the same object that is supplied to Func<T>
Assert.AreEqual(reader, dataReader);
// Finally true returned from Func<T> is attained in return
Assert.IsTrue(result);

Finally, our test method will be as shown in Exhibit #13

Exhibit #13

[Test]
public void Retrieve_CanRetrieve()
{
    //Arrange
    var connectionManager = Substitute.For<IConnectionManager>();
    var configurationManagerFacade = Substitute.For<IConfigurationManagerFacade>();
    var logger = Substitute.For<ILogger>();
    var repository = new Repository(connectionManager, configurationManagerFacade, logger);


    var connectionString = "TestConnectionString";
    var storedProcedureName = "TestStoredProcedure";
    configurationManagerFacade.GetConnectionString("defaultConnection").Returns(connectionString);
    var mockConnection = Substitute.For<IDbConnection>();
    connectionManager.CreateConnection(connectionString).Returns(mockConnection);
    var mockCommand = Substitute.For<IDbCommand>();
    connectionManager.CreateCommand(storedProcedureName).Returns(mockCommand);
    var reader = Substitute.For<IDataReader>();
    mockCommand.ExecuteReader().Returns(reader);


    //Act
    IDataReader dataReader = null;
    var result = repository.Retrieve(storedProcedureName, (dr) => 
    {
        dataReader = dr;
        return true;
    });


    //Assert
    Received.InOrder(() =>
    {
        mockCommand.Connection = mockConnection;
        mockConnection.Open();
        mockCommand.ExecuteReader();
        mockConnection.Close();
    });


    logger.DidNotReceive().Trace(Arg.Any<string>());
    Assert.AreEqual(reader, dataReader);
    Assert.IsTrue(result);
}



In this test, we ensure all the statements are executed in the target method except the Trace. Since it is very important to cover 100% of our code unity test, we should create another test method in turn sets the success variable to a false and thus, making the condition to log the statement. 

How I check my unit test coverage?

There are free tools available to check the code coverage locally on a visual studio IDE and I use AxoCover to see the test coverage. I have seen architects happy with 80% or more of a coverage, in most cases I could hit 100% by following the practices we have discussed earlier in this article (especially isolation techniques).

How do I test my unit tests?

Here is the most important part before we could conclude writing tests against a target method. I personally would make changes to my code under test and see my tests fails on any impacting change that I made. In our example as in Exhibit #8, my test fails if I comment out any one line of code or I change assignment of any of those variables, by not calling a function or even I change the order of invocations. Try these on your target method and ensure any of these action is failing your tests.


I bet, this way of writing strict unit tests keeps our code clean and green forever. Thanks!

0
1,075 Views

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.