Tuesday, 6 August 2024

Mocking in .NET with Moq

 





ntroduction

There are several mocking frameworks to be used in testing environments, such as NMock, RhinoMocks, FakeItEasy, and Moq, to isolate units to be tested from the underlying dependencies. Although Moq is a relatively new mocking framework, this framework has been adapted by the developers because it's very easy to use, not following the traditional mock pattern Record/Replay, which is very opaque and unintuitive; it supports full VS Intellisense when creating the mock objects as well as it supports the new features of Microsoft.NET 2.0/3.5/4.0 such as dynamic typing, lambda expressions and LINQ expressions in C#. So, you, as a developer, will have a very low learning curve when using mocking frameworks.

I will explain how to use this amazing mocking framework in this article.

Suppose we need to build a Calculator that provides basic arithmetic operations and a currency conversion operation (in this case, converting a dollar to Chilean pesos according to the actual exchange rate).

Let's define the ICalculator interface (see Listing 1).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CalculatorPkg
{
    public interface ICalculator
    {
        int Add(int param1, int param2);
        int Subtract(int param1, int param2);
        int Multipy(int param1, int param2);
        int Divide(int param1, int param2);
        int ConvertUSDtoCLP(int unit);
    }
}
C#

Listing 1

Let's suppose that we will consume an external service that provides the actual exchange rate for the USD and CLP. Let's define the IUSD_CLP_ExchangeRateFeed interface (see Listing 2).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MoneyExchangeRatePkg
{
    public interface IUSD_CLP_ExchangeRateFeed
    {
        int GetActualUSDValue();
    }
}
C#

Listing 2

Now let's define the Calculator class to realize the ICalculator interface. Let's use Dependency Injection programming techniques to inject an object realizing the IUSD_CLP_ExchangeRateFeed interface using the constructor of the Calculator class. Finally, let's implement each class method (see Listing 3).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MoneyExchangeRatePkg;

namespace CalculatorPkg
{
    public class Calculator : ICalculator
    {
        private IUSD_CLP_ExchangeRateFeed _feed;
        public Calculator(IUSD_CLP_ExchangeRateFeed feed)
        {
            this._feed = feed;
        }
        #region ICalculator Members
        public int Add(int param1, int param2)
        {
            throw new NotImplementedException();
        }
        public int Subtract(int param1, int param2)
        {
            throw new NotImplementedException();
        }
        public int Multipy(int param1, int param2)
        {
            throw new NotImplementedException();
        }
        public int Divide(int param1, int param2)
        {
            return param1 / param2;
        }
        public int ConvertUSDtoCLP(int unit)
        {
            return unit * this._feed.GetActualUSDValue();
        }
        #endregion
    }
}
C#

Listing 3

Now let's prepare the testing environment for the Calculator component. We're going to use the NUnit testing framework and Moq mocking framework.

To get started with NUnit, you just need to download the framework from http://www.nunit.org/ and install it.

To start with Moq, you just need to download the framework in a zip archive from http://code.google.com/p/moq/ and extract it to a location to reference the Moq.dll assembly from your testing environment.

Then, we create a testing library, add the references to NUnit and Moq frameworks, and add the tester class CalculatorTester to define the test cases.

Moq is a very easy-to-use mocking framework. To define the mock objects, we use generics passing the interface as the type. The behavior of the mock objects is done using basically a set of lambda expressions, making the code more productive and type-safe (see Listing 4).

Mock<IUSD_CLP_ExchangeRateFeed> mockObject = new Mock<IUSD_CLP_ExchangeRateFeed>();
mockObject.Setup(m => m.GetActualUSDValue()).Returns(500);
IUSD_CLP_ExchangeRateFeed value = mockObject.Object;
C#

Listing 4

The final code for the testing cases is shown in Listing 5.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Step 1. Add a reference to the NUnit.Framework namespace
using NUnit.Framework;
// Step 2. Add a reference to the Moq namespace
using Moq;
// Step 3. Add a reference to the CalculatorPkg namespace
using CalculatorPkg;
// Step 4. Add a reference to the MoneyExchangeRatePkg namespace
using MoneyExchangeRatePkg;

namespace CalculatorPkg.Tests
{
    // Step 5. Add the attribute annotating the class as a tester
    [TestFixture]
    public class CalculatorTester
    {
        // Step 6. Add the definition of the mock objects
        private IUSD_CLP_ExchangeRateFeed prvGetMockExchangeRateFeed()
        {
            Mock<IUSD_CLP_ExchangeRateFeed> mockObject = new Mock<IUSD_CLP_ExchangeRateFeed>();
            mockObject.Setup(m => m.GetActualUSDValue()).Returns(500);
            return mockObject.Object;
        }
        // Step 7. Add the test methods for each test case
        [Test(Description="Divide 9 by 3. Expected result is 3.")]
        public void TC1_Divide9By3()
        {
            IUSD_CLP_ExchangeRateFeed feed = this.prvGetMockExchangeRateFeed();
            ICalculator calculator = new Calculator(feed);
            int actualResult = calculator.Divide(9,3);
            int expectedResult = 3;
            Assert.AreEqual(expectedResult, actualResult);
        }
        [Test(Description = "Divide any number by zero. Should throw an System.DivideByZeroException exception.")]
        [ExpectedException(typeof(System.DivideByZeroException))]
        public void TC2_DivideByZero()
        {
            IUSD_CLP_ExchangeRateFeed feed = this.prvGetMockExchangeRateFeed();
            ICalculator calculator = new Calculator(feed);
            int actualResult = calculator.Divide(9, 0);
        }
        [Test(Description = "Convert 1 USD to CLP. Expected result is 500.")]
        public void TC3_ConvertUSDtoCLPTest()
        {
            IUSD_CLP_ExchangeRateFeed feed = this.prvGetMockExchangeRateFeed();
            ICalculator calculator = new Calculator(feed);
            int actualResult = calculator.ConvertUSDtoCLP(1);
            int expectedResult = 500;
            Assert.AreEqual(expectedResult, actualResult);
        }
    }
}

No comments:

Post a Comment