/ dependenciesinterfacemockingmoq

Mocking Dependencies (silly dependency! um... not that kind of mocking)

The code in this post is also available on Github.

In a mood at work the other day (after hunting down some obscure bug that would’ve been more apparent, had I had some relevant failing tests to point me in the right direction), I started back-filling old code with unit tests. That meant removing some dependencies on things I didn’t actually need to test, so I’ll post a few (contrived) examples.

Don’t Rely on External Dependencies

One of the most critical pieces of unit testing (although not overly complicated, as I’ll show) is mocking out external dependencies. You really want to focus on a single well-defined piece of functionality, as much as possible.

  • Do not rely on any external resources, such as accessing the hard drive, network shares, emails servers, etc.
  • Do not run code asynchronously unless the code under test also waits for it to complete. Otherwise the test may (sometimes) finish before the asynchronous process completes, causing the test to (sometimes) fail.
  • Tests should be consistent, reliable and completely in your control. When they fail, you should know exactly why.
  • Tests become less portable as you have to rely on everyone who might run them having access to the same external resources.

The idea is that you don’t want to suddenly find your test fails because a network drive is unavailable. Or a location on disk can’t be written to. Or an SMTP server is down. (Testing end-to-end functionality is a different kind of test, usually referred to as an integration test.)

Example 1: Mocking Your Own Classes

We have a class called Logger, with a single method allowing us to log a message to a file on disk.

Now we want to test the PerformSomeAction() method, making sure a message is logged. Here’s our first attempt at the code and unit test.

using System.IO;
using System.Linq;
using NUnit.Framework;
namespace MockingDependencies052014
{
    public class Example1
    {
        public const string LogFile = @"c:\test1.txt";
        public const string JobCompleteMessage = "Job Complete!";
        private readonly Logger1 logger;

        public Example1()
        {
            logger = new Logger1(LogFile);
        }

        public void PerformSomeAction()
        {
            // Do Something logger.LogMessage(JobCompleteMessage);
        }
    }
    public class Logger1
    {
        private readonly string logFile;

        public Logger1(string logFile)
        {
            this.logFile = logFile;
        }

        public void LogMessage(string message)
        {
            using (var file = new StreamWriter(logFile, true))
            {
                file.WriteLine(message);
            }
        }
    }
    [TestFixture]
    class Example1Tester
    {
        [Test]
        public void PerformSomeAction_LogsMessage_WhenActionSuccessfullyCompletes()
        {
            var vm = new Example1();
            vm.PerformSomeAction();
            var expectedValue = Example1.JobCompleteMessage;
            var actualValue = File.ReadAllLines(Example1.LogFile).Last();
            Assert.AreEqual(expectedValue, actualValue);
        }
    }
}

The above code has several issues:

  • It instantiates Logger, which writes the message to disk and may cause the test to unexpectedly fail (i.e. an IOException).
  • It tests for the expected value by reading from the disk, which may also cause the test to fail.
  • We only wanted to test a single method in the MainWindowViewModel class, but as a side-effect we’re testing our Logger class (which should have its own, separate tests), as well as StreamWriter.WriteLine() and File.ReadAllLines()(proven methods of the .NET Framework that do not need to be tested by us).

Example 1b: Mocking the Interface Your Class Implements

We can decouple from that dependency by implementing an interface on our Logger class, and programming against the interface. Below I’ve made several changes to the code:

  • Added an ILogger interface which the Logger class implements.
  • Changed the class-level “logger” variable to an ILogger.
  • Added an overloaded constructor to MainWindowViewModel that accepts an ILogger.

This gives us the option of either instantiating a new Logger at runtime, or passing in a mocked version of the interface for testing purposes.

using System.IO;
using Moq;
using NUnit.Framework;
namespace MockingDependencies052014
{
    public class Example1b
    {
        private const string LogFile = @"c:\test1b.txt";
        public const string JobCompleteMessage = "Job Completed again!";
        private readonly ILogger1b logger;

        public Example1b()
        {
            this.logger = new Logger1b(LogFile);
        }

        public Example1b(ILogger1b logger)
        {
            this.logger = logger;
        }

        public void PerformSomeAction()
        {
            // Do Something logger.LogMessage(JobCompleteMessage);
        }
    }

    public interface ILogger1b
    {
        void LogMessage(string message);
    }

    public class Logger1b : ILogger1b
    {
        private readonly string logFile;

        public Logger1b(string logFile)
        {
            this.logFile = logFile;
        }

        public void LogMessage(string message)
        {
            using (var file = new StreamWriter(logFile, true))
            {
                file.WriteLine(message);
            }
        }
    }

    [TestFixture]
    class Example1bTester
    {
        private Mock<ILogger1b> logger;

        [SetUp]
        public void Setup()
        {
            logger = new Mock<ILogger1b>();
        }

        [Test]
        public void PerformSomeAction_LogsMessage_WhenActionSuccessfullyCompletes()
        {
            var vm = new Example1b(logger.Object);
            vm.PerformSomeAction();
            logger.Verify(x => x.LogMessage(Example1b.JobCompleteMessage), Times.Once());
        }
    }
}

The above tests uses a mocking framework called Moq to mock the interface. We can pass that to the code being tested, and verify whether or not certain methods ran.

  • We’re no longer writing to / reading from disk, and we’re no longer testing .NET Framework components.
  • We’re simply running the method in question and making sure our own LogMessage() method ran, one time, and was passed the expected message.

There’s an additional method in the above test, named Setup. You can name the method whatever you want, but it must be adorned with NUnit’s “SetUp” attribute. Any code in there runs before each and every* *test, guaranteeing a new instance of Mock, so we don’t have side-effects carrying over from previous test. There’s also a “TearDown” attribute to run a method *after *each test (to perform some sort of clean up, for instance).

Example 2a: Mocking Other (Unsealed) Classes (i.e. XDocument)

If a class is not sealed, you can just extend it and add an interface that includes those methods / properties that you need to use, and wish to mock. For example, let’s assume we have a program that opens a settings file, changes some of the values in it, and saves it back to disk:

using System.IO;
using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
namespace MockingDependencies052014
{
    public class Example2a
    {
        public const string SettingsFile = @"c:\test2a.xml";
        public const int TheAnswerToLifeUniverseAndEverything = 42;
        private readonly XDocument xDoc;

        public Example2a()
        {
            // We need a file with a root element to read or we'll get an error
            File.WriteAllText(SettingsFile, @"<?xml version=""1.0"" encoding=""utf-8"" ?><root><someNodeName /></root>");
            xDoc = XDocument.Load(SettingsFile);
        }

        public void WriteSettingsFile()
        {
            if (!xDoc.Descendants("someNodeName").Any())
                return;
            foreach (var desc in xDoc.Descendants("someNodeName"))
                desc.SetValue(TheAnswerToLifeUniverseAndEverything); xDoc.Save(SettingsFile);
        }
    }

    [TestFixture]
    class Example2aTester
    {
        [Test]
        public void WriteSettingsFile_SavesFile()
        {
            var vm = new Example2a();
            vm.WriteSettingsFile();
            var xDoc = XDocument.Load(Example2a.SettingsFile);
            var insertedValue = xDoc.Descendants().Single(x => x.Name == "someNodeName").Value;
            Assert.AreEqual(Example2a.TheAnswerToLifeUniverseAndEverything, int.Parse(insertedValue));
        }
    }
}

When we test this, we’ll end up reading from and writing to disk again, and testing the XDocument class this time.

This is unnecessary and could result in the test failing unexpectedly due to conditions outside our immediate control.

Since this particular class in the .NET Framework is not sealed, we can extend it and create our own interface.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Moq;
using NUnit.Framework;

namespace MockingDependencies052014
{
    public class Example2a_2
    {
        public const string SettingsFile = @"c:\test2a_2.xml";
        public const int TheAnswerToLifeUniverseAndEverything = 42;

        private readonly IXDocument xDoc;

        public Example2a_2()
        {
            File.WriteAllText(SettingsFile,
                @"<?xml version=""1.0"" encoding=""utf-8"" ?><root><someNodeName /></root>");

            xDoc = XDocumentExt.Load(SettingsFile);
        }

        public Example2a_2(IXDocument iXDocument, string initialXmlString = null)
        {
            File.WriteAllText(SettingsFile,
                initialXmlString ?? @"<?xml version=""1.0"" encoding=""utf-8"" ?><root><someNodeName /></root>");

            xDoc = iXDocument.LoadEx(SettingsFile);
        }

        public void WriteSettingsFile()
        {
            if (!xDoc.Descendants("someNodeName").Any())
                return;

            foreach (var desc in xDoc.Descendants("someNodeName"))
                desc.SetValue(TheAnswerToLifeUniverseAndEverything);

            xDoc.Save(SettingsFile);
        }
    }

    public interface IXDocument
    {
        IXDocument LoadEx(string fileName);

        void Save(string fileName);

        IEnumerable<XElement> Descendants(XName name);
    }

    public class XDocumentExt : XDocument, IXDocument
    {
        public IXDocument LoadEx(string fileName)
        {
            return Load(fileName);
        }

        public new static IXDocument Load(string fileName)
        {
            return (IXDocument)XDocument.Load(fileName);
        }

        public new void Save(string fileName)
        {
            base.Save(fileName);
        }

        public new IEnumerable<XElement> Descendants(XName name)
        {
            return base.Descendants(name);
        }
    }

    [TestFixture]
    class Example2a_2Tester
    {
        Mock<IXDocument> xDoc;

        [SetUp]
        public void Setup()
        {
            xDoc = new Mock<IXDocument>();
        }

        [Test]
        public void WriteSettingsFile_DoesNotSaveFile_WhenNoNodesAvailable()
        {
            xDoc.Setup(x => x.LoadEx(Example2a_2.SettingsFile))
                .Returns(xDoc.Object);
            xDoc.Setup(x => x.Descendants("someNodeName"))
                .Returns(new List<XElement>());

            var vm = new Example2a_2(xDoc.Object);

            vm.WriteSettingsFile();

            xDoc.Verify(x => x.Save(Example2a_2.SettingsFile), Times.Never);
        }

        [Test]
        public void WriteSettingsFile_SavesFile_WhenNodesAvailable()
        {
            xDoc.Setup(x => x.LoadEx(Example2a_2.SettingsFile))
                .Returns(xDoc.Object);
            xDoc.Setup(x => x.Descendants("someNodeName"))
                .Returns(new List<XElement> {new XElement("someNodeName")});

            var vm = new Example2a_2(xDoc.Object);

            vm.WriteSettingsFile();

            xDoc.Verify(x => x.Save(Example2a_2.SettingsFile), Times.Once());
        }
    }
}

With these changes (creating an interface and programming against it) we can now:

  • Test the code without touching the physical disk.
  • Test the code without also testing the XDocument class.

The first test says that when we request all descendants named “someNodeName”, we should get an empty list (indicating no such nodes exist), then verifies that Save() is never called.

The second test says that when we request all descendants named “someNodeName”, we should get a list with one such node in it. Our code then sets a value on that node, and we can verify that Save() is called exactly once.

We’re in control of the object returned by the LoadEx() method and we can verify whether we saved without actually saving anything.

Example 2b: Mocking Other (Unsealed) Classes (i.e. SmtpClient)

Here’s another example. I just wanted to try doing this with SmtpClient too, since I mentioned it earlier. Testing it’s possible I suppose, but it could fail in myriad ways.

using System.Net.Mail;
using NUnit.Framework;
namespace MockingDependencies052014
{
    public class Example2b
    {
        private readonly SmtpClient client;

        public Example2b()
        {
            client = new SmtpClient("smtp.somehost.com", 465);
        }

        public void ProcessShipment()
        {
            // Do some work related to processing shipments 
            var message = new MailMessage("shipping@acme.com", "wecoyote@rr.com", "Your order has shipped!", "Your order has shipped!");
            client.Send(message);
        }

        [TestFixture]
        class MainWindowViewModelTester
        {
            [Test]
            public void ProcessShipment_SendsMessage()
            {
                var vm = new Example2b();
                vm.ProcessShipment();
                // How to test if mail was sent?? 
                // Could change ProcessShipment() to return a boolean.
                // Or connect to mail server and check for email... lol, uh no 
            }
        }
    }
}

Now we’ll modify the code, introducing an ISmtpClient interface and a new class that derives from SmtpClient and implements the interface. We’ll modify the test too, mocking the dependency and verifying that Send() is called exactly once (by verifying that Send() was called on the interface, not the SmtpClient itself, so no mail is actually transmitted).

using System.Net.Mail;
using Moq;
using NUnit.Framework;

namespace MockingDependencies052014
{
    public class Example2b
    {
        private readonly ISmtpClient client;

        public Example2b()
        {
            client = new SmtpClientEx("smtp.somehost.com", 465);
        }

        public Example2b(ISmtpClient client)
        {
            this.client = client;
        }

        public void ProcessShipment()
        {
            // Do some work related to processing shipments

            var message = new MailMessage("shipping@acme.com", "wecoyote@rr.com", "Your order has shipped!", "Your order has shipped!");

            client.Send(message);
        }

        public interface ISmtpClient
        {
            void Send(MailMessage message);
        }

        public class SmtpClientEx : SmtpClient, ISmtpClient
        {
            public SmtpClientEx(string host, int port)
                : base(host, port) { }
        }

        [TestFixture]
        public class Example2bTester
        {
            private Mock<ISmtpClient> clientMock;

            [SetUp]
            public void Setup()
            {
                clientMock = new Mock<ISmtpClient>();
            }

            [Test]
            public void ProcessShipment_SendsMessage()
            {
                var vm = new Example2b(clientMock.Object);

                vm.ProcessShipment();

                clientMock.Verify(x => x.Send(It.IsAny<MailMessage>()), Times.Once());
            }
        }
    }
}

Example 3: Mocking Other (Sealed) Classes or Static Methods

If you have a class that is sealed, you can’t extend it. And if you’re using methods that are static, you cannot mock them because you cannot instantiate them.

In either case, you’ll need to wrap the functionality you want to test. This time we’ll use the File class to create a small program that simply reads all lines starting with a specific user name.

The only way to test this is to hit the disk, which leaves you without total control over the test environment.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;

namespace MockingDependencies052014
{
    public class Example3
    {
        public const string LogFile = @"c:\test3.txt";
        public const string LogFileTimeStamp = "Timestamp: ";

        public Example3()
        {
            File.WriteAllText(LogFile, LogFileTimeStamp);
        }

        public IEnumerable<string> GetLogFileEntries(string logPath)
        {
            return File.ReadAllLines(logPath)
                       .Where(x => x.StartsWith(LogFileTimeStamp));
        }
    }

    [TestFixture]
    public class Example3Tester
    {
        [Test]
        public void GetLogFileEntries_ReturnsLogEntries()
        {
            var vm = new Example3();

            var results = vm.GetLogFileEntries(Example3.LogFile);

            Assert.AreEqual(results.Count(), File.ReadAllLines(Example3.LogFile)
                                                 .Count(x => x.StartsWith(Example3.LogFileTimeStamp)));
        }
    }
}

There’s no reason to be testing the File.ReadAllLines() method directly.

To fix this, we can wrap the static method in a new class, complete with an interface, so that we have complete control over the input, and can test the output in multiple ways if we want.

In the following test, we mock out the File class and specify exactly what data the ReadAllLines() method should return. Then we can make sure our code filters the data correctly (returning only lines that start with “Timestamp: “), without actually testing the File class, which we didn’t create and don’t need to test.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;

namespace MockingDependencies052014
{
    public class Example3b
    {
        public const string LogFile = @"c:\test3b.txt";
        public const string LogFileTimeStamp = "Timestamp: ";

        private readonly IFile file;

        public Example3b()
        {
            file = new FileEx();

            file.WriteAllText(LogFile, LogFileTimeStamp);
        }

        public Example3b(IFile file)
        {
            this.file = file;

            file.WriteAllText(LogFile, LogFileTimeStamp);
        }

        public IEnumerable<string> GetLogFileEntries(string logPath)
        {
            return file.ReadAllLines(logPath)
                       .Where(x => x.StartsWith(LogFileTimeStamp));
        }
    }

    public interface IFile
    {
        IEnumerable<string> ReadAllLines(string path);

        void WriteAllText(string path, string contents);
    }

    public class FileEx : IFile
    {
        public IEnumerable<string> ReadAllLines(string path)
        {
            return File.ReadAllLines(path);
        }

        public void WriteAllText(string path, string contents)
        {
            File.WriteAllText(path, contents);
        }
    }

    [TestFixture]
    public class Example3bTester
    {
        private Mock<IFile> fileMock;

        [SetUp]
        public void Setup()
        {
            fileMock = new Mock<IFile>();
        }

        [Test]
        public void GetLogFileEntries_ReturnsLogEntries()
        {
            var fileContents = new List<string>
                               {
                                   "Timestamp: 03:30 - FileNotFoundException occurred",
                                   "Some informational message, blah.",
                                   "Timestamp: 13:22 - User MJ123 access denied."
                               };

            fileMock.Setup(x => x.ReadAllLines(Example3b.LogFile))
                    .Returns(fileContents);

            var vm = new Example3b(fileMock.Object);

            var results = vm.GetLogFileEntries(Example3b.LogFile);

            Assert.AreEqual(results.Count(), 2);
        }
    }
}

The code in this post is also available on Github.


Grant Winney

Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.

Read More