dcsimg
 

Fundamentals of Mock Objects in Unit Testing

Wednesday Dec 26th 2018 by Tapas Pal

Learn the fundamentals of mocking with a C# examples. You'll also learn how it helps in writing unit tests, and the different ways to do mocking.

Introduction

Mock is an object to simulates the behavior of a real method/object in a controlled environment for unit testing. A developer learns mocking as a skillset and uses mock frameworks as a tool during unit testing. Mock objects help to isolate the component being tested from the components it depends on by applying mock objects effectively. Mocking is an important part of test-driven development (TDD). In this article, I'll demonstrate fundamentals of mocking with a C# examples, how it helps in writing unit tests, and the different ways to do mocking.

Creating Mock Objects for C# Unit Tests

To demonstrate Mocking concept with a Dependency Injection design pattern, we will manually create a mock object. Let's create a Visual Studio Console application and name it 'SampleMockProject', as demonstrated in Figure 1.

Visual Studio New Project
Figure 1: Visual Studio New Project

Next, we will add a Unit Testing Project to the solution created in the previous step. From the file menu, select new -> Project (see Figure 2).

Visual Studio Add New Unit Test Project
Figure 2: Visual Studio Add New Unit Test Project

Select Unit Test project from the available list of templates. Name the project 'SampleUnitTestProject'. Please consult Figure 3.

Select Project Template
Figure 3: Select Project Template

Next, we will add the MOQ NuGet package. You could find the library files online (Moq's are at https://github.com/moq/moq4), download them, and add them as a reference to your project. However, NuGet Package Manager also could help you to install them.

Right-click the project and select "Manage NuGet Packages…". You also can select from the Tools menu, as depicted in Figure 4.

Add NuGet Package
Figure 4: Add NuGet Package

Select the MOQ package from the list of available packages and install it. This is shown in Figure 5.

During installation, make sure you select both Console and Unit test projects.

MOQ NuGet Package
Figure 5: MOQ NuGet Package

After successful installation, all MOQ required assemblies will be added in the reference. See Figure 6.

MOQ NuGet Package Installed
Figure 6: MOQ NuGet Package Installed

Next, we will add the IStudentDataMapper.cs file in the console project. This interface defines the functions needed in the object that is "injected" into the CreateNewStudent function—by both the "real" object (which accesses the database), and the "mock" object (without a database). Refer to the following code snippet.

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

namespace Engine.Moq
{
   public interface IStudentDataMapper
   {
      bool StudentNameExistsInDatabase(string name);
      void InsertNewStudentIntoDatabase(string name);
   }
}

Next, we will add the MockStudentDataMapper.cs file to the console project. This class implements IStudentDataMapper, and accesses the database. Following is the MockStudentDataMapper.cs file code snippet.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Moq;

namespace TestEngine.Moq.ManualMock

{
   public class MockStudentDataMapper : IStudentDataMapper
   {
      public bool ResultToReturn { get; set; }

      public bool StudentNameExistsInDatabase(string name)
      {
         return ResultToReturn;
      }

      public void InsertNewStudentIntoDatabase(string name)
      {
      }

   }
}

The following CreateNewStudent function of the Student class does some validation, by using the "injected" IStudentDataMapper object.

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

namespace Engine.Moq
{
   public class Student
   {
      public string Name { get; private set; }
      public int Age { get; private set; }
      public int Marks { get; private set; }

      public Student(string name, int age, int marks)
      {
         Name = name;
         Age = age;
         Marks = marks;
      }

      public static Student CreateNewStudent(string name,
         IStudentDataMapper studentDataMapper = null)
      {

         if (studentDataMapper == null)
         {
            studentDataMapper = new StudentDataMapper();
         }

         if (string.IsNullOrWhiteSpace(name))
         {
            throw new ArgumentException("Student name cannot be
               empty.");
         }

         if (studentDataMapper.StudentNameExistsInDatabase(name))
         {
            throw new ArgumentException("Student name already
               exists.");
         }

         studentDataMapper.InsertNewStudentIntoDatabase(name);

         return new Student(name, 0, 10);
      }
   }
}

Next, we will add the StudentDataMapper.cs file to manually create the mock class to use in the unit tests.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Data;

namespace Engine.Moq
{
   public class StudentDataMapper : IStudentDataMapper
   {
      private readonly string _connectionString =
         "Data Source=(local);Initial Catalog=Student;Integrated
         Security=True";

      public bool StudentNameExistsInDatabase(string name)
      {
         using (SqlConnection connection = new
            SqlConnection(_connectionString))
         {
            connection.Open();

            using (SqlCommand studentsWithThisName =
               connection.CreateCommand())
            {
               studentsWithThisName.CommandType = CommandType.Text;
               studentsWithThisName.CommandText = "SELECT count(*)
                  FROM Student WHERE 'Name' = @Name";

               studentsWithThisName.Parameters.AddWithValue
                  ("@Name", name);
               var existingRowCount = (int)studentsWithThisName
                  .ExecuteScalar();
               return existingRowCount > 0;
            }
         }
      }

      public void InsertNewStudentIntoDatabase(string name)
      {
         using (SqlConnection connection = new SqlConnection
            (_connectionString))
         {
            connection.Open();

            using (SqlCommand studentsWithThisName =
               connection.CreateCommand())
            {
               studentsWithThisName.CommandType = CommandType.Text;
               studentsWithThisName.CommandText = "INSERT INTO
                  Student ([Name]) VALUES (@Name)";

               studentsWithThisName.Parameters.AddWithValue
                  ("@Name", name);

               studentsWithThisName.ExecuteNonQuery();
            }
         }
      }
   }
}

Next, for unit tests, use MockStudentDataMapper objects to eliminate the need to connect to a database when running automated tests. Following is the list of Test methods.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Engine.Moq;

namespace TestEngine.Moq.ManualMock
{
   [TestClass]
   public class TestStudent
   {
      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_EmptyName()
      {
         Student student = Student.CreateNewStudent("");
      }

      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_EmptyName_MockedDataMapper()
      {
         MockStudentDataMapper mockStudentDataMapper = new
            MockStudentDataMapper();

         Student student = Student.CreateNewStudent("",
            mockStudentDataMapper);
      }

      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_AlreadyExistsInDatabase()
      {
         MockStudentDataMapper mockStudentDataMapper = new
            MockStudentDataMapper();

         mockStudentDataMapper.ResultToReturn = true;

         Student student = Student.CreateNewStudent("Test",
            mockStudentDataMapper);
      }

      [TestMethod]
      public void Test_CreateNewStudent_DoesNotAlreadyExist
         InDatabase()
      {
         MockStudentDataMapper mockStudentDataMapper = new
            MockStudentDataMapper();

         mockStudentDataMapper.ResultToReturn = false;

         Student student = Student.CreateNewStudent("Test",
            mockStudentDataMapper);

         Assert.AreEqual("Test", student.Name);
         Assert.AreEqual(13, student.Age);
         Assert.AreEqual(10, student.Marks);
      }
   }
}

Now, to use the MOQ library, we need to add "using Moq;" and other required Unit testing references in the Unit test project -> Test class.

In the unit tests, instead of creating an object of the old MockStudentDataMapper, and passing it into the Student.CreateNewStudent function, we will use MOQ to create the mock object. We could rewrite all the Test methods accordingly. Refer to the following Test method code.

You can execute all the Test methods to see the result.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Moq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace TestEngine.Moq.UsingMoqLibrary
{
   [TestClass]
   class TestStudentWithMock
   {
      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_EmptyName()
      {
         Student student = Student.CreateNewStudent("");
      }

      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_EmptyName_MockedDataMapper()
      {
         var mock = new Mock<IStudentDataMapper>();
         Student student = Student.CreateNewStudent("",
            mock.Object);
      }

      [TestMethod]
      [ExpectedException(typeof(ArgumentException))]
      public void Test_CreateNewStudent_AlreadyExistsInDatabase()
      {
         var mock = new Mock<IStudentDataMapper>();
         mock.Setup(x => x.StudentNameExistsInDatabase
            (It.IsAny<string>())).Returns(true);

         Student student = Student.CreateNewStudent("Test",
            mock.Object);
      }

      [TestMethod]
      public void Test_CreateNewStudent_DoesNotAlreadyExist
         InDatabase()
      {
         var mock = new Mock<IStudentDataMapper>();
         mock.Setup(x => x.StudentNameExistsInDatabase
            (It.IsAny<string>())).Returns(false);

         Student student = Student.CreateNewStudent("Test",
            mock.Object);

         Assert.AreEqual("Test", student.Name);
         Assert.AreEqual(13, student.Age);
         Assert.AreEqual(10, student.Marks);
      }
   }
}

Conclusion

In this article, I've shown you how to use MOQ as the mocking framework in testing environments of a .NET solution.

I hope you enjoyed reading the article. That's all for today; happy reading!

Home
Mobile Site | Full Site