This article is a brief introduction to the use of unit testing in MVC 4 using Entity Framework with Repository Pattern.
What is Unit testing: The basic purpose of unit testing is to test the methods of business logic of ASP.NET or MVC projects.
What is Entity Framework
Entity Framework (EF) is an object-relational mapper that enables .NET developers to work with relational data using domain-specific objects. It eliminates the need for most of the data-access code that developers usually need to write. For more see: http://msdn.microsoft.com/en-us/data/ef.aspx
What is Repository Pattern in MVC
The Repository Pattern is useful for decoupling entity operations from presentation, which allows easy mocking and unit testing.
"The Repository will delegate to the appropriate infrastructure services to get the job done. Encapsulating in the mechanisms of storage, retrieval and query is the most basic feature of a Repository implementation" .... "Most common queries should also be hardcoded to the Repositories as methods." For more see: http://webmasterdriver.blogspot.in/2012/05/what-is-repository-pattern-in-aspnet.html
Getting Started
Create a new Project. Open Visual Studio 2012.
Go to "File" => "New" => "Project...".
Select "Web" in installed templates.
Select "ASP.NET MVC 4 Web Application".
Enter the Name and choose the location.
Click "OK".
Image 1.
After clicking the OK button in the next wizard there is a check box for creating a unit test project. If you want to create a unit test project then enable that check box, you can add it later or also add a new item feature.
Image 2.
Image 3.
First of all we are going to add a new ADO.NET Entity Data Model and provide it a relevant name.
Image 4.
In the next wizard select "Generate from database" and click Next and create a new connection and select a database name. In my sample I am making a sqlexpress connection and my database is located in the App_Data folder. The attached sample has a NORTHWND database.
Image 5.
Image 6.
Image 7.
Image 8.
Image 9.
Image 10.
As you can see the connection string has been added to the web.config file:
- <connectionStrings>
- <add name="NORTHWNDEntities" connectionString="metadata=res://*/Models.NORHWNDModel.csdl|res://*/Models.NORHWNDModel.ssdl|res://*/Models.NORHWNDModel.msl;provider=System.Data.SqlClient;provider connection string="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\NORTHWND.MDF;integrated security=True;connect timeout=30;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings>
Image 11.
Now add a new class in models "EmployeeRepository" and start working on it. The following is my code:
- public class EmployeeRepository : IEmployeeRepository
- {
-
-
-
- private NORTHWNDEntities _db = new NORTHWNDEntities();
-
-
-
-
- public IEnumerable<Employee> GetAllEmployee()
- {
- return _db.Employees.ToList();
- }
-
-
-
-
-
- public Employee GetEmployeeByID(int id)
- {
- return _db.Employees.FirstOrDefault(d => d.EmployeeID == id);
- }
-
-
-
-
- public void CreateNewEmployee(Employee employeeToCreate)
- {
- _db.Employees.Add(employeeToCreate);
- _db.SaveChanges();
-
- }
-
-
-
-
- public int SaveChanges()
- {
- return _db.SaveChanges();
- }
-
-
-
-
- public void DeleteEmployee(int id)
- {
- var conToDel = GetEmployeeByID(id);
- _db.Employees.Remove(conToDel);
- _db.SaveChanges();
- }
- }
As you can see, this class is inheriting an IEmployeeRepository interface. Now add a new interface.
- public interface IEmployeeRepository
- {
- IEnumerable<Employee> GetAllEmployee();
- void CreateNewEmployee(Employee employeeToCreate);
- void DeleteEmployee(int id);
- Employee GetEmployeeByID(int id);
- int SaveChanges();
- }
We are good so far. It is now time to add a new controller in the Controllers directory.
Image 12.
My controller name is EmployeeController; see:
Now add Views by right-clicking on ViewResult in the controller and select strongly typed view and select model class which is created by a data model and select scaffold template and click Add.
Image 13.
Image 14.
You can add views in the same way for Create, Delete, Details. Edit, Empty and List scaffold templates.
Now let's run the application to see the result.
The Index view will show you the listing of all employees:
Image 15.
Click on create link that will create new employees:
Image 16.
To edit an existing employee, click on the edit link:
Image 17.
To see the details of an employee click on the details link:
Image 18.
To delete an employee's record click on the delete botton:
Image 19.
We are now good with the MVC application using a repository and the Entity Framework.
Now it is time to work on Unit Testing. First of all create a models directory in the test project. As you will see, the MVC project library was added to the test project. If you are adding a new test project to the solution then you have to add the library manually.
Tests/Models/InMemoryEmployeeRepository.cs
First of all add a namespace for this class, as in:
- using UnitTestingInMVCUsingEF.Models;
- class InMemoryEmployeeRepository : IEmployeeRepository
- {
- private List<Employee> _db = new List<Employee>();
- public Exception ExceptionToThrow { get; set; }
- public IEnumerable<Employee> GetAllEmployee()
- {
- return _db.ToList();
- }
- public Employee GetEmployeeByID(int id)
- {
- return _db.FirstOrDefault(d => d.EmployeeID == id);
- }
- public void CreateNewEmployee(Employee employeeToCreate)
- {
- if (ExceptionToThrow != null)
- throw ExceptionToThrow;
- _db.Add(employeeToCreate);
- }
- public void SaveChanges(Employee employeeToUpdate)
- {
- foreach (Employee employee in _db)
- {
- if (employee.EmployeeID == employeeToUpdate.EmployeeID)
- {
- _db.Remove(employee);
- _db.Add(employeeToUpdate);
- break;
- }
- }
- }
- public void Add(Employee employeeToAdd)
- {
- _db.Add(employeeToAdd);
- }
- public int SaveChanges()
- {
- return 1;
- }
- public void DeleteEmployee(int id)
- {
- _db.Remove(GetEmployeeByID(id));
- }
- }
Now let's add a new controller in the Tests/Controllers/ EmployeeControllerTest class, as in:
- using UnitTestingInMVCUsingEF.Models;
- using UnitTestingInMVCUsingEF.Controllers;
- using UnitTestingInMVCUsingEF.Tests.Models;
- [TestClass]
- public class EmployeeControllerTest
- {
-
-
-
- [TestMethod]
- public void IndexView()
- {
- var empcontroller = GetEmployeeController(new InMemoryEmployeeRepository());
- ViewResult result = empcontroller.Index();
- Assert.AreEqual("Index", result.ViewName);
- }
-
-
-
-
-
- private static EmployeeController GetEmployeeController(IEmployeeRepository emprepository)
- {
- EmployeeController empcontroller = new EmployeeController(emprepository);
- empcontroller.ControllerContext = new ControllerContext()
- {
- Controller = empcontroller,
- RequestContext = new RequestContext(new MockHttpContext(), new RouteData())
- };
- return empcontroller;
- }
-
-
-
- [TestMethod]
- public void GetAllEmployeeFromRepository()
- {
-
- Employee employee1 = GetEmployeeName(1, "Beniwal", "Raj", "Mr", "H33", "Noida", "U.P", "201301");
- Employee employee2 = GetEmployeeName(2, "Beniwal", "Pari", "Ms", "d77", "Noida", "U.P", "201301");
- InMemoryEmployeeRepository emprepository = new InMemoryEmployeeRepository();
- emprepository.Add(employee1);
- emprepository.Add(employee2);
- var controller = GetEmployeeController(emprepository);
- var result = controller.Index();
- var datamodel = (IEnumerable<Employee>)result.ViewData.Model;
- CollectionAssert.Contains(datamodel.ToList(), employee1);
- CollectionAssert.Contains(datamodel.ToList(), employee2);
- }
-
-
-
-
-
-
-
-
-
-
-
-
- Employee GetEmployeeName(int id, string lName, string fName, string title, string address, string city, string region, string postalCode)
- {
- return new Employee
- {
- EmployeeID = id,
- LastName = lName,
- FirstName = fName,
- Title = title,
- Address = address,
- City = city,
- Region = region,
- PostalCode = postalCode
- };
- }
-
-
-
- [TestMethod]
- public void Create_PostEmployeeInRepository()
- {
- InMemoryEmployeeRepository emprepository = new InMemoryEmployeeRepository();
- EmployeeController empcontroller = GetEmployeeController(emprepository);
- Employee employee = GetEmployeeID();
- empcontroller.Create(employee);
- IEnumerable<Employee> employees = emprepository.GetAllEmployee();
- Assert.IsTrue(employees.Contains(employee));
- }
-
-
-
-
- Employee GetEmployeeID()
- {
- return GetEmployeeName(1, "Beniwal", "Raj", "Mr", "H33", "Noida", "U.P", "201301");
- }
-
-
-
- [TestMethod]
- public void Create_PostRedirectOnSuccess()
- {
- EmployeeController controller = GetEmployeeController(new InMemoryEmployeeRepository());
- Employee model = GetEmployeeID();
- var result = (RedirectToRouteResult)controller.Create(model);
- Assert.AreEqual("Index", result.RouteValues["action"]);
- }
-
-
-
- [TestMethod]
- public void ViewIsNotValid()
- {
- EmployeeController empcontroller = GetEmployeeController(new InMemoryEmployeeRepository());
- empcontroller.ModelState.AddModelError("", "mock error message");
- Employee model = GetEmployeeName(1, "", "", "", "","","","");
- var result = (ViewResult)empcontroller.Create(model);
- Assert.AreEqual("Create", result.ViewName);
- }
-
-
-
- [TestMethod]
- public void RepositoryThrowsException()
- {
-
- InMemoryEmployeeRepository emprepository = new InMemoryEmployeeRepository();
- Exception exception = new Exception();
- emprepository.ExceptionToThrow = exception;
- EmployeeController controller = GetEmployeeController(emprepository);
- Employee employee = GetEmployeeID();
- var result = (ViewResult)controller.Create(employee);
- Assert.AreEqual("Create", result.ViewName);
- ModelState modelState = result.ViewData.ModelState[""];
- Assert.IsNotNull(modelState);
- Assert.IsTrue(modelState.Errors.Any());
- Assert.AreEqual(exception, modelState.Errors[0].Exception);
- }
- private class MockHttpContext : HttpContextBase
- {
- private readonly IPrincipal _user = new GenericPrincipal(new GenericIdentity("someUser"), null );
- public override IPrincipal User
- {
- get
- {
- return _user;
- }
- set
- {
- base.User = value;
- }
- }
- }
- }
It is now time to run the test cases.
Image 20.
Image 21.
As you will see all test cases are the result.