Improved Unit Testing with Moq and Dependency Injection

A quick definition for Unit Testing: Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. (emphasis mine)

In past experiences, I’ve seen unit tests that do not even closely resemble that definition, classes that are impossible to test, and an approach to feature development with disregard for how it fits into a meaningful test process.

Unit tests will never be the be all, end all, to a well tested application. Among other categories are system tests, integration tests, automated UI testing, etc. The focus here will be solely on practices and patterns for not only structuring classes that can be easily tested, but also structuring the test suite so that it can be easier to maintain and update when features change.

A Simple Example

The Class

    public class VersionDetails
    {
        public VersionDetails(string filePath)
        {
            FileName = Path.GetFileName(filePath);
            FileSize = new FileInfo(filePath).Length;

            var fileStream = new  FileStream(filePath, FileMode.Open);
            using (var streamReader = new StreamReader(fileStream))
            {
                var versionNumber = streamReader.ReadLine();
                VersionNumber = new Version(versionNumber);
            }
        }

        public string FileName { get; }

        public long FileSize { get; }

        public Version VersionNumber { get; }
    }

The Tests

    [TestClass]
    public class UnitTests
    {
        [TestMethod]
        public void Load()
        {
            var expectedVersion = new Version(1,2,3,4);
            var versionDetails = new VersionDetails("C:\\Temp\\FilePath1.ver");

            Assert.AreEqual("FilePath1.ver", versionDetails.FileName);
            Assert.AreEqual(300L, versionDetails.FileSize);
            Assert.AreEqual(expectedVersion, versionDetails.VersionNumber);
        }
    }

We have a simple class that takes in a file path and populates the file name, how large the file is in bytes, and loads the version number by parsing the first line of the file. To test it, I’ve added a simple load test that takes a filepath from the local disk and verifies the size and names are populated successfully.

Even with such a simple example, there are so many things that need to be addressed to make this test, and this class, useful. Right off the bat, here’s a few issues we need to mitigate.

  • Uses static methods for obtaining name, size, and version loading.
  • The unit test method intent is vague.
  • Because the test is dependent on static methods external to the class, this is more of an integration test.
  • The test is dependent on a file that exists on my developer machine.

Based on the issues listed, this class and test set is fragile to external influences that it has no control over. What happens if the test file is in a restricted location for the test runner? What happens if the test runner has insufficient privileges to read the file? This class also depends on a Windows file system. This class could be ported to Linux or OSX using Mono, again this test would break.

A better approach

To make this class truly unit testable as per our original definition, consider what this class is responsible for. Getting the file name, the size of the file, and a version number from the first line of the file. What it is not responsible for: how the file name, size, and version string are obtained. For those, we delegate to other dependencies (Path, FileInfo, and a FileStream). These are the dependencies that we have no control over their implementations.

Let’s look at one approach for getting around these limitations:

    public interface IFileService
    {
        string GetFileName(string filePath);

        long GetFileSize(string filePath);

        StreamReader Open(string filePath);
    }

We have created an interface that we control and that our class will adhere to and consume. It provides a way to get the file name, file size, and the ability to open a stream so the first line can be parsed.

Our updated class:

    public class VersionDetails
    {
        public VersionDetails(string filePath, IFileService fileService)
        {
            FileName = fileService.GetFileName(filePath);
            FileSize = fileService.GetFileSize(filePath);

            using (var streamReader = fileService.Open(filePath))
            {
                var versionNumber = streamReader.ReadLine();
                VersionNumber = new Version(versionNumber);
            }
        }

        public string FileName { get; }

        public long FileSize { get; }

        public Version VersionNumber { get; }
    }

Why this is better:

  • No static methods.
  • An interface that a unit test can provide a primitive implementation for.
  • This class is no longer dependent on FileInfo, Path and FileStreams.
  • It’s also no longer dependent on the windows file system.

The default FileService implementation will likely delegate to Path, FileInfo and FileStreams for it’s base implementation. However, we can also provide a LinuxFileService, OSXFileService, MonoFileService, etc. The primary point is that the VersionDetails class no longer cares, it is given a dependency that provides those details.

The Updated Test Suite

Our original set of tests left a lot to be desired. Most notably, it tested everything in a single method. Three different places of failure were covered under a single test. There needs to be a single unit covered per test. This could possibly lead to a lot of scaffolding for setting up each test. Let’s structure our test suite a little to provide a convenient way to maintain our tests.

   [TestClass]
    public class VersionDetailsTests
    {
        private string mFilePath;
        private IFileService mFileService;

        [TestInitialize]
        public void Initialize()
        {
            mFilePath = "Z:\\ThisPathDoesNotHaveToExist\\Test.ver";
            mFileService = new TestFileService();
        }

        [TestMethod]
        public void FileSizeIsCorrectAfterLoading()
        {
            // What our test service is hard coded to.
            var expectedSize = 37L;

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedSize, uut.FileSize);
        }

        [TestMethod]
        public void LoadingAFileWillReportTheCorrectFileVersion()
        {
            var expectedVersion = new Version(1, 2, 3, 4);

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedVersion, uut.VersionNumber);
        }

        [TestMethod]
        public void FileNameIsCorrectlyLoadedFromFileService()
        {
            // Note that our mock service is just a pass through
            // for whatever value is provided here.
            var expectedFilePath = mFilePath;

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedFilePath, uut.FileName);
        }

        private VersionDetails CreateUnitUnderTest()
        {
            return new VersionDetails(mFilePath, mFileService);
        }

        private class TestFileService : IFileService
        {
            public string GetFileName(string filePath)
            {
                return filePath;
            }

            public long GetFileSize(string filePath)
            {
                return 37L;
            }

            public StreamReader Open(string filePath)
            {
                return new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes("1.2.3.4")));
            }
        }
    }

The main important points from this updated set of tests:

  • There is a new private factory method called CreateUnitUnderTest().
  • The class that is being tested is stored as a uut variable.
  • There is a fake TestFileService.
  • Each important part of the class is tested by its own test method.
  • Each test method is titled explicitly for what the intent of the test hopes to achieve.

Every single set of tests I write will follow this basic pattern. An Initialize() method that is decorated with a TestAttribute that will be run before each test. This helps to keep each test method execution isolated from other test method results as each test should not be dependent on other tests being ran. It also helps to establish a base set of initialization calls that will be relevant to each test method.

The CreateUnitUnderTest() seems simple at first glance, but it isolates the class that is going to be tested and how it’s created. More complex classes may have more than one dependency that needs to be injected in order to provide unit tests. This method makes it so that each TestMethod can do some type of extra initialization relevant to the test, for example, maybe this test method needs to verify that if the FileService throws an exception, the unit under test will handle it as expected. Also, by consistently labeling the class being tested as uut, it makes it clear what class is being tested. This makes our Assert statements always follow a pattern of expected, actual where actual should always be something from our uut.

Then after the initialization state is complete, calling CreateUnitUnderTest() gives you a fully, ready to be tested class with all dependencies provided. It really helps to cleanup the TestMethod’s logic and make it simple to read. Less scaffolding, expectations are set, the unit is exercised, and then we verify and assert what we expect to happen, happened.

An even better approach

Our suite of tests are now readable and structured in a way to make updates and changes easier to maintain. The limitations are still apparent in our above suite of tests:

  • New TestFileService will need to be provided if we ever want to verify different functionality.
  • These stubs will now be the long poll in the tent with regards to maintenance.
  • Each Stub could potentially need it’s own file in our project. Each Stub only providing a small difference to exercise a particular unit’s functionality.

We’ve basically moved the maintenance from our class to be tested, onto our suite of tests. Lots of scaffolding that would only serve to exercise a small piece of specific code would be necessary. Let’s look at some ways to reduce this complexity. First let’s discuss a solution generically.

Mocks

Our TestFileService could be considered a Mock, or Fake, or a Stub. Any of these terms are describing a temporary implementation to facilitate some type of test. I’m a fan of ubiquitous language, so from here forward, I will refer to it as a Mock.

Obviously in our improved approach above, we’d need to provide different implementations for each test depending on what the test needed. Like I mentioned earlier, if I wanted one of the methods to throw an exception, I’d have to provide a full interface implementation for the Mock solely to provide the one test. This can be burdensome and thankfully there are lots of Mocking Frameworks out there that help reduce the number of lines of code we need to write to spin up mocks for our tests. Just to name a few: Rhino Mocks, NSubstitute, and Moq.

I use Moq on a daily basis, but any mocking framework can be used. Let’s look at turning our TestFileService into a Mock using Moq. The easiest way to get started with Moq is to use the NuGet package manager to add the latest Moq package to your test project.

        private Mock<IFileService> mFileService;

        [TestInitialize]
        public void Initialize()
        {
            mFileService = new Mock<IFileService>();
            mFileService.Setup(o => o.GetFileSize(It.IsAny<string>())).Returns(37L);
            mFileService.Setup(o => o.GetFileName(It.IsAny<string>())).Returns<string>(path => path);
            mFileService.Setup(o => o.Open(It.IsAny<string>())).Returns(new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes("1.2.3.4"))));
        }

Let’s break the important parts down. Moq has a number of concepts to use when creating mocks. It gives the ability to provide Method details, which is done through the Setup() call. It takes a lambda expression for what method we want to mock, and as part of that we can also provide particular parameters for that mocked method and how to react.

In the above, I use the It.IsAny() call for all my method parameters. This basically means use this mocked method no matter what parameter is provided. So if we provide our filePath, or we provide a gibberish value, it will still return what is configured. Lastly, there is a Returns() method. This tells the mock what value to be returned when this method is executed.

The only anomaly that stands out above is the following:

            mFileService.Setup(o => o.GetFileName(It.IsAny<string>())).Returns<string>(path => path);

In the Returns statement, I have it use a lambda expression that takes a string as an argument. What this accomplishes is the path argument is what was passed in to the GetFileName method. If I call GetFileName(“Frank”), then “Frank” will be returned from the mocked method. It’s a way to allow your return statements to be influenced by the parameters passed into it.

That’s it. I have now removed the need to define the TestFileService and I have an equivalent implementation of that within my mocked IFileService. Let’s look at our updated suite of tests now:

     [TestClass]
    public class VersionDetailsTests
    {
        private string mFilePath;
        private Mock<IFileService> mFileService;

        [TestInitialize]
        public void Initialize()
        {
            mFilePath = "Z:\\ThisPathDoesNotHaveToExist\\Test.ver";

            mFileService = new Mock<IFileService>();
            mFileService.Setup(o => o.GetFileSize(It.IsAny<string>())).Returns(37L);
            mFileService.Setup(o => o.GetFileName(It.IsAny<string>())).Returns<string>(path => path);
            mFileService.Setup(o => o.Open(It.IsAny<string>())).Returns(new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes("1.2.3.4"))));
        }

        [TestMethod]
        public void FileSizeIsCorrectAfterLoading()
        {
            // What our test service is hard coded to.
            var expectedSize = 37L;

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedSize, uut.FileSize);
        }

        [TestMethod]
        public void LoadingAFileWillReportTheCorrectFileVersion()
        {
            var expectedVersion = new Version(1, 2, 3, 4);

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedVersion, uut.VersionNumber);
        }

        [TestMethod]
        public void FileNameIsCorrectlyLoadedFromFileService()
        {
            // Note that our mock service is just a pass through
            // for whatever value is provided here.
            var expectedFilePath = mFilePath;

            var uut = CreateUnitUnderTest();

            Assert.AreEqual(expectedFilePath, uut.FileName);
        }

        [TestMethod]
        [ExpectedException(typeof(FileNotFoundException))]
        public void FileNotFoundExceptionThrownWhenFileDoesntExist()
        {
            mFileService.Setup(o => o.GetFileName(It.IsAny<string>())).Throws<FileNotFoundException>();
            var uut = CreateUnitUnderTest();
        }

        private VersionDetails CreateUnitUnderTest()
        {
            return new VersionDetails(mFilePath, mFileService.Object);
        }
    }

Looks almost exactly like our original except now we use mFileService.Object. That’s Moq’s way of giving access to the Mocked implementation. But notice we’ve added an additional test to our suite. The FileNotFoundExceptionThrownWhenFileDoesntExist test does a setup call that overrides the default implementation. It says when the GetFileName method is called, it will throw a FileNotFoundException. Our test is configured to expect that exception to be thrown. This is the major benefit to using a mocking framework like Moq. We’ve now dynamically changed how our Mock functions just for this one test. We didn’t have to stub out a new TestFileServiceThatThrowsException
implementation for just this one test.

Wrapping Up

We’ve taken a class that was originally dependent on static hardcoded implementations that depended on file systems and details we could not control and updated it to be able to be truly unit tested. That is, no matter what implementations are provided for IFileService our class consumes it as we’d expect.

What this doesn’t solve: integration issues. Our original concerns are still valid, what happens when you don’t have the proper access to the file system that you need? There will still need to be integration tests that exercise this class and ensure it behaves on the expected deployment environment. Just because we have 100% passing unit tests, doesn’t meant we have a bug free system, it only helps to ensure that the behavior and interactions we establish between our classes are maintained and respected.

When writing class implementations in the future, I’d like for you to be cognizant of the following:

  • Is this testable at the unit level?
  • What are my dependencies necessary for this class?
  • How can these dependencies be mocked?

Anywhere in your class implementations where there are explicit static calls, new child object creations, or new threads created, these are going to cause inherent hurdles to overcome with regards to unit testing. Hopefully our above example serves as a starting point to setting up maintainable test code facilitated by a unit testable class implementation. By using the CreateUnitUnderTest factory method, we are able to keep our test implementation clean and readable across all of our test suite. It should always be apparent that our uut is what is being tested.

In a later post, we’ll explore more of the ways that Moq makes our unit tests more robust and maintainable. We’ll also cover ways to mitigate more complex classes that use events/observables/background tasks, etc. to help make them unit testable.