This is another article in this series. In our previous article, we covered various concepts of unit testing in a Visual Studio unit test project and in third-party unit test libraries like Nunit. You can read those here.
We know that dependency injection is one of the important parts of application development when we want to do de-coupled architecture. Another point behind de-coupling architecture is unit testing. Unit testing is tremendously easy when we implement Dependency injection in applications.
The purpose of this article is to explain how dependency injection helps unit testing in applications. So the fundamental idea of unit testing is needed and in this example, we will implement a Mock object using the Moq framework, so a prior concept of mocking will help you to understand the article fully. To make DB operations we will use Entity Framework.
Ok, first of all, let me clarify a principle of unit testing. "The tests may change for code but the code will never change for tests.“". The principle is, that we can change the code of a unit test for applications but the application code will not be changed for a unit test.
Now, let's create a DLL class library application and add the following code to it. In this example, we will implement the repository design pattern.
Create an interface for CRUD operation
Here is the interface creation that we will implement in our repository. Have a look that we have only one function in the interface, we have purposefully made it simple to understand.
public interface ICompany
{
Boolean InsertCompany(company tmpcompany);
}
We will now implement the interface in the repository, this repository will contain DB operation code. Here is the repository class. We have used the Entity Framework to insert data but in reality, we will not insert data because we will use a mock object of the CompanyOperationRepo class.
public class CompanyOperationRepo : ICompany
{
efDBEntities _db = new efDBEntities();
public Boolean InsertCompany(company tmpcompany)
{
if (tmpcompany.company_name != null)
using (_db)
{
_db.company.Add(tmpcompany);
_db.SaveChanges();
return true;
}
throw new ArgumentException();
}
}
Fine, we have created the repository, and now we will call this repository from the actual function. This class definition is a bit interesting. Here we have implemented Dependency injection. We know that there are many ways to implement dependency injection and we have implemented constructor injection in this scenario.
public class CompanyClass
{
ICompany company = null;
public CompanyCRUD(ICompany tmpCompany)
{
company = tmpCompany;
}
public Boolean InsertCompany(company Company)
{
return company.InsertCompany(Company);
}
}
Ok, we have set up our application, now we can set up the unit test application to test the code. Just add one unit test project to the same solution and provide a reference to it. In this example, we have used Moq as the mock framework, so please give a Moq reference from the NuGet package manager.
using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCApplication.Controllers;
using ConsoleApp;
using Moq;
namespace TestMVC
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
//arrange
var company = new company() { company_name = "TCS" };
var mockRepo = new Mock<ICompany>();
mockRepo.Setup(x => x.InsertCompany(company)).Returns(true);
var companyObject = new Company(mockRepo.Object);
var retrnData = companyObject.InsertCompany(company);
}
}
}
Have a look that how we have implemented the mock object of the ICompany interface in the following line. Then it will mimic the object of all the classes that will implement the ICompany interface.
var mockRepo = new Mock<ICompany>();
Now, we are setting up the function that we want to bypass using the mock object here.
mockRepo.Setup(x => x.InsertCompany(company)).Returns(true);
The return parameter is always true, as we set it. So, if we call the InsertCompany function, it will return true always without executing it.
Now, if we run it, we will see that the test case will be passed. As we expect.
Fine, now we will rewrite our previous application without dependency injection and then we will implement the test case again.
Now, in this case, we have removed the interface part and implemented the repository class as in the following.
public class CompanyOperationRepo
{
efDBEntities _db = new efDBEntities();
public Boolean InsertCompany(company tmpcompany)
{
if (tmpcompany.company_name != null)
{
using (_db)
{
_db.company.Add(tmpcompany);
_db.SaveChanges();
return true;
}
}
throw new ArgumentException();
}
}
This is the concrete class, where we have used the repository to do DB-related operations. Please look that we are instantiating an object that is a repository class within the concrete class, that is not at all recommended in terms of the best practice.
public class Company
{
CompanyOperationRepo company = null;
public CompanyCRUD(CompanyOperationRepo tmpCompany)
{
company = tmpCompany;
}
public Boolean InsertCompany(company Company)
{
return company.InsertCompany(Company);
}
}
Here is the unit test code.
using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCApplication.Controllers;
using ConsoleApp;
using Moq;
namespace TestMVC
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var company = new company { company_name = "TCS" };
var mockRepo = new Mock<CompanyOperationRepo>();
mockRepo.Setup(x => x.InsertCompany(company)).Returns(true);
var objectCRUD = new CompanyCRUD(mockRepo.Object);
var data = objectCRUD.InsertCompany(company);
Assert.AreEqual(data, true);
}
}
}
If we run the unit test we will encounter the exception.
It's saying that it's not possible to implement a mock object of a non-virtual function. So we need to make it virtual to make it run.
Now, If we change our code to make the code run, it's again of the Unit test-principal. As we said, we cannot change the code for the test at any cost.
Anyway, for demonstration purposes, let's change the code. Here is the modified version of the code.
public virtual Boolean InsertCompany(company tmpcompany)
{
if (tmpcompany.company_name != null)
{
using (_db)
{
_db.company.Add(tmpcompany);
_db.SaveChanges();
return true;
}
}
throw new ArgumentException();
}
The test will now pass.
So, we have seen that if we do not use Dependency injection then we need to change the code and that is not a good practice at all.
Conclusion
In this article, I have shown how dependency injection helps in unit testing and mocking. I hope you have understood it.
We are using mock as a mock framework, though there are many mock frameworks like "TypeMock" that work with even non-virtual functions.