Obligatory Unit Testing Post

Any developer worth his salt well tell you that unit testing is sweet. In fact unit tested code will simply help you sleep better, it’s that good. Sometimes, though, its easy to get bogged down in a project’s deadlines and skip the testing altogether. To avoid this I try to use tools that make the testing portion as easy as possible. Two of the best tools that I’ve found for .NET unit testing are XUnit and Moq, both available through nuget.

XUnit and Moq are a powerful combination. XUnit is, in my opinion, the friendliest testing framework for .NET (for various reasons) and Moq is the friendliest mocking framework for .NET (also for various reasons). That being said, sometimes when unit testing with mocks you might spend a lot of time writing scaffolding setup code for behavior which an existing concrete class already handles. So how can you take advantage of mocking while leveraging your existing classes, without everything turning into a mess?

This post proposes a rather simple solution to get the best of both worlds. The easiest way to show it is by example… so lets begin.

Assume the HardWoker class is the class under test. This class has three dependencies which are represented by some concrete classes at runtime. The constructor is shown below.


public class HardWorker
{
    private readonly IDependencyOne _depOne;
    private readonly IDependencyTwo _depTwo;
    private readonly IDependencyThree _depThree;

    public HardWorker(IDependencyOne depOne, 
                      IDependencyTwo depTwo,
                      IDependencyThree depThree)
    {
        _depOne = depOne;
        _depTwo = depTwo;
        _depThree = depThree;
    }
}

The work the class does is irrelevant, all that matters is that the class needs its dependencies to perform any of the work. When it comes time to writing the test class I propose to include a ‘Creator’ method. What does a creator method do? Well it creates things… specifically an instance of the class under test. A portion of the test class is shown below, creator method here is CreatehardWorker.


public class HardWorkerTests
{
    private readonly Mock _depOne = new Mock();
    private readonly Mock _depTwo = new Mock();
    private readonly Mock _depThree = new Mock();

    private HardWorker CreateHardWorker(IDependencyOne depOne = null,
                                        IDependencyTwo depTwo = null,
                                        IDependencyThree depThree = null)
    {
        if (depOne == null)    depOne = _depOne.Object;
        if (depTwo == null)    depTwo = _depTwo.Object;
        if (depThree == null)  depThree = _depThree.Object;

        return new HardWorker(depOne, depTwo, depThree);
    }
}

The method’s parameters are all the required dependencies with assigned values of null. Notice how we keep instances of mocked dependencies as members and all the method does is assign mock objects to a dependency parameter when one isn’t provided. The pattern for any test class is the same:

  • Receive all dependencies
  • Make sure to receive them empty by default
  • Replace any empty ones with mocks
  • Create new instance with received parameters

Almost done, so what can what does this allow us to do? Look at the test method below.


[Fact]
public void RandomFunctionalityTest()
{
    _depOne.Setup(dep => dep.DoSomething(It.IsAny()))
        .Returns(true);

    _depThree.Setup(dep => dep.DoSomethingElse(It.IsAny()))
        .Returns(40);

    var worker = this.CreateHardWorker(depTwo: new ConcreteDependency());

    worker.DoWork();

    ... (Assert away!) ...
}

Using named arguments we can call the CreateHardWorker method with an instance
of any dependency that we don’t want to mock for this test while still using all the other dependencies which we did setup. All of this works thanks to the silent hero, XUnit, which creates a class instance for each test method and therefore allows us to reconfigure the mocked member variables without worry for each test.

Next time you find yourself typing line after line of setup code stop and think if maybe this pattern applies to you.

Leave a Reply

Your email address will not be published. Required fields are marked *

*