Unit Testing with Mocks

By: Chris Dunn

If you are interested in an academic, or more intellectual understanding of Mocks, Stubs, Fakes which collectively are known as Test Doubles, it's always a good idea to visit Martin Fowler. Stubs Aren't Fakes.

For this post I want to talk about testing in isolation using Mocks and Dependency Injection.  The examples in this post will be using Moq(https://github.com/moq/moq) but there a number of other options out there to choose from.

Making your code testable

In order to unit test in isolation, you need to plan ahead.  If your code is tightly coupled to a specific concrete instance of an implementation, you will not be able to isolate the functionality.  This is where we need to consider the concept of Dependency Inversion.

Let's look at some code for a Insurance Quoting Application.  I worked for a large insurance company for a number of years and can promise you the logic is much more complicated than the following.  But it will work for our purposes.

public class QuoteService 
    {

        public double GetQuote(int age, char gender, bool smoker)
        {
            //base rate
            double quoteRate = 10;

            //rate modifiers
            if (age >= 40) quoteRate *= 1.1;
            if (gender == 'M') quoteRate *= 1.2;
            if (smoker) quoteRate *= 2;

            //return quote
            return quoteRate;
        }
    }

public class QuoteManager
    {

        public double GetQuote(int age, char gender, bool smoker)
        {

            var service = new QuoteService();

            return service.GetQuote(age, gender, smoker);

        }


    }

In the preceding code, I have created a QuoteManager class which makes a call to a service QuoteService.  In a live situation the QuoteService would probably be calling a remote service which returns the result.  The QuoteService is just a proxy.  As you can see, the GetQuote method of QuoteManager is directly instantiating a new instance of the QuoteService class.  Based on this design, the QuoteManager has a direct dependency on the QuoteService class.  If the QuoteService calls a remote service, I would be unable to test QuoteManager without calling that remote service.  That being the case, I can't test this method in isolation.  I would also be testing the remote service, internet/intranet connection, and perhaps back-end database to lookup quotes.  See how tightly coupled I have made the design?

Decouple

With some slight changes to our design, we can decouple the elements of our quote application and separate the interface of the QuoteService from it's implementation.

///
    /// Quote Service Interface
    ///
    public interface IQuoteService
    {
        double GetQuote(int age, char gender, bool smoker);
    }

    ///
    /// Concrete implementation of IQuoteService
    ///
    public class QuoteService : IQuoteService
    {
      
        public double GetQuote(int age, char gender, bool smoker)
        {
            //base rate
            double quoteRate = 10;

            //rate modifiers
            if (age >= 40) quoteRate *= 1.1;
            if (gender == 'M') quoteRate *= 1.2;
            if (smoker) quoteRate *= 2;

            //return quote
            return quoteRate;
        }
    }

    ///
    /// Wrapper class that calls Quote Service
    ///
    public class QuoteManager
    {
        private IQuoteService _service;

        public QuoteManager(IQuoteService service)
        {
            _service = service;
        }

        public double GetQuote(int age, char gender, bool smoker)
        {
            //calls Quote Service
            return _service.GetQuote(age, gender, smoker);

        }


    }

First, what I've done is extract the interface from QuoteService into IQuoteService. This let's us work with the QuoteService abstractly instead of a concrete implementation. I have then implemented the IQuoteService which is what we would use in our application, to call the remote service quoting service.

Within the QuoteManager, rather than "newing up" an instance of QuoteService, we can instead pass in an instance of a concrete implementation of the interface when we create the QuoteManager. I have't changed the functionality, just how it's implemented.

When I want to get a quote, I do the following.

    var quoteManager = new QuoteManager(new QuoteService());
    var rate = quoteManager.GetQuote(34,'F',false);

And even better still is to use Dependency Injection like that built into .net core to declare the concrete implementation in ConfigureServices in Startup.cs, and then by specifying a parameter of type IQuoteService in my controller's constructor, DI will inject the concrete instance of QuoteManager it into my controller class, ready to use.

 
        public void ConfigureServices(IServiceCollection services)
        {
             services.AddSingleton(new QuoteManager());
        }
 
public class MyController: Controller
{
     private IQuoteService _service;

     public MyController(IQuoteService service)
     {
           this._service = service;
     }

    public IActionResult Index()
    {
           var rate = _service.GetQuote(34,'F',false);  
     }

}

The point of all of this is that now we can change the implementation of the QuoteService used by QuoteManager so we can provide a mocked implementation to allow us to test the GetQuote in isolation.

Unit Testing

Now that our code is designed with testing in mind, the actual testing is much easier. For the following example I am using MSTest for simplicity sake. I generally recommend nUnit or xUnit but MSTest works just fine.

I always recommend setting up a separate project for your test cases. It keeps the code base of the main application clean. Below, I've created a standard test class with the attribute [TestClass]. If you're familiar with MSTest you might often use the attribute [TestMethod] when delcaring a test method. In this case I want to pass arguments into my test method to run multiple test scenerios without needing to write a separate method for each data set. I do this by decorating the method with [DataTestMethod], and specify the test data with [DataRow ({data goes here}). I then need to have a cooresponding parameter in the method signature for each data value passed into DataRow.

 
    [TestClass]
    public class MoqTests
    {
               
        [DataTestMethod]
        [DataRow(30,'M',false,12)]
        [DataRow(40,'F',false,22)]
        public void GetQuote_ApprovedParams_ValidQuote(int age, char gender, bool smoker, int result)
        {
           //testing goes here.

        }


    }

Remember when we setup the QuoteManager to allow us to specify the concrete implementation of the quoting service? This is where the design pays us back in allowing us to test in isolation. I could create a new class that skips the remote service calls and just returns some fixed values for quoted rates. But that involves creating a new type and bloating my code. There are cases where a custom implementation serves a purpose, like in the use of Fakes for a in-memory database. For the purpose of a mocked service, we'll be using a (you guessed it) Mock.

A Mock lets you dynamically build a test double for an implementation simply for test purposes. It lets you decide which functionality to implement and how. It lets you define input and return values for methods. Take a look at the code below. This is our test method.

 

            //arrange
            var mock = new Mock<IQuoteService>();
            mock.Setup(m => m.GetQuote(30, 'M', false)).Returns(12);
            mock.Setup(m => m.GetQuote(35, 'F', true)).Returns(22);

            IQuoteService service = mock.Object;

            //act 
             QuoteManager quoteManager = new QuoteManager(service);
             var quote = quoteManager.GetQuote(age, gender, smoker);

            //assert
            Assert.AreEqual(result, quote);

First, I am creating a new Mock based on the IQuoteService interface. Then, I am using the Setup method to assign logic to the GetQuote method call. I am telling Mock, for the given parameters, return this value. When I am done filling in the logic, I call the .Object method, which returns an implementation of the type I specified (IQuoteService).

The .Setup is but one call in the MOQ library. There are many and I promise I will come back and do a tutorial on more general MOQ usage in the future. Until then checkout their Quickstart.

Next, we instantiate a new QuoteManager, passing into the constructor the QuoteService implementation we Mocked up. When we call GetQuote on the QuoteManager, it simply returns the value 12 (for the make non-smoker), and 22 (for the female smoker). It doesn't make any remote service calls and doesn't have any external dependencies. We are truly testing the QuoteManager's GetQuote in isolation.

Tags: c# unit testing dependency injection moq mock

Copyright 2023 Cidean, LLC. All rights reserved.

Proudly running Umbraco 7. This site is responsive with the help of Foundation 5.