Introduction
In this article we will talk about creating unit testable applications using ASP.NET MVC.
We will also talk a little about test driven development and see how we can design and develop
ASP.NET MVC application in such a way that they are easily testable.
Background
Test Driven Development(TDD)
is gaining momentum with each passing day. The reason for this is that
with TDD we can not only built robust applications but also have a proof that the application is
working correctly from a functional perspective by having successfully unit test cases on our modules.
Also, the projects following agile/scrum methodology find TDD very useful because the major challenge
in scrum is to define when our "done" is done. If we follow TDD approach then we know that when
all our test cases are passed, we are done.
ASP.NET MVC application provide a very good support for TDD because we can write unit test directly against
our controllers without needing any web server. Doing so we can be sure that the controllers are
working correctly from a functionality perspective.
In the rest of the article we will discuss how to design and develop MVC applications that
are easily testable from unit testing perspective.
Using the code
The first thing that we need to understand is that the Controller
class can be instantiated like normal
classes. This is unlike the WebForms
application where the pages can only be invoked by web server. This possibility of being able to instantiate the Controller classes open the door for writing test cases
that will directly test the controllers logic without needing any web server.
We will now create a simple MVC application that will perform CRUD operations on a table. We will
design this application in such a way that the Controller are fully testable with dummy data without even
touching the actual database.
Database
Let us create a very simple database containing one table as follows. We will perform CRUD operations
on this table from our MVC application.
Data Access
For data access we will use entity framework. The benefit of using entity framework is that it
will generate all the data access logic for us and will also generate entities for the respective tables
that we can use as Models
in our application. The generated entities for our sample database will look like.
Repository and Unit of Work
Now we have the ObjectContext
and entities ready with us. We can very well use the ObjectContext
class
directly in our controller to perform database operations.
public ActionResult Index()
{
List<Book> books = null;
using (SampleDatabaseEntities entities = new SampleDatabaseEntities())
{
books = entities.Books.ToList();
}
return View(books);
}
But if we do this our controller is not testable. The reason is that when we instantiate the controller
class and call the index function, the Context
class will be created and it will hit the actual database.
Now how can we solve this problem. This problem can be solved by implementing Repository and Unit of Work
pattern. If we have only one Repository class containing the ObjectContext
then we don't need Unit of Work but
if we have multiple repository classes then we will need unit of work class too.
Note: It is highly recommended to read the following article before reading this. Understanding Repository
and Unit Of Work pattern is essential to create testable MVC applications. Please read this details: Understanding and Implementing Repository and Unit of Work Pattern in ASP.NET MVC Application[^]
Let us create a simple interface defining the contract for accessing the books data.
we will then implement this interface in our repository class class. the benefit of doing this
is that we can then have another class implementing the same interface but playing around with the dummy data.
Now as long as the controller is using the Interface our test projects can pass the dummy data class.
We will create and pass the dummy class to the controller from our test project.
public interface IBooksRepository
{
List<Book> GetAllBooks();
Book GetBookById(int id);
void AddBook(Book book);
void UpdateBook(Book book);
void DeleteBook(Book book);
void Save();
}
And the concrete repository class that performs the actual database operations will look like:
public class BooksRepository : IBooksRepository
{
SampleDatabaseEntities entities = null;
public BooksRepository(SampleDatabaseEntities entities)
{
this.entities = entities;
}
public List<Book> GetAllBooks()
{
return entities.Books.ToList();
}
public Book GetBookById(int id)
{
return entities.Books.SingleOrDefault(book => book.ID == id);
}
public void AddBook(Book book)
{
entities.Books.AddObject(book);
}
public void UpdateBook(Book book)
{
entities.Books.Attach(book);
entities.ObjectStateManager.ChangeObjectState(book, EntityState.Modified);
}
public void DeleteBook(Book book)
{
entities.Books.DeleteObject(book);
}
public void Save()
{
entities.SaveChanges();
}
}
Now the Responsibility of the UnitOfwork
class will be to create the ObjectContext
and
Repository
class and pass them to the controller.
public class UnitOfWork
{
private SampleDatabaseEntities entities = null;
public UnitOfWork()
{
entities = new SampleDatabaseEntities();
BooksRepository = new BooksRepository(entities);
}
public UnitOfWork(IBooksRepository booksRepo)
{
BooksRepository = booksRepo;
}
public IBooksRepository BooksRepository
{
get;
private set;
}
}
Our controller can then use this UnitofWork
class to perform the database operations as:
public class BooksController : Controller
{
private UnitOfWork unitOfWork = null;
public BooksController()
: this(new UnitOfWork())
{
}
public BooksController(UnitOfWork uow)
{
this.unitOfWork = uow;
}
public ActionResult Index()
{
List<Book> books = unitOfWork.BooksRepository.GetAllBooks();
return View(books);
}
public ActionResult Details(int id)
{
Book book = unitOfWork.BooksRepository.GetBookById(id);
return View(book);
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Book book)
{
if (ModelState.IsValid)
{
unitOfWork.BooksRepository.AddBook(book);
unitOfWork.BooksRepository.Save();
return RedirectToAction("Index");
}
return View();
}
public ActionResult Edit(int id)
{
Book book = unitOfWork.BooksRepository.GetBookById(id);
return View(book);
}
[HttpPost]
public ActionResult Edit(Book book)
{
if (ModelState.IsValid)
{
unitOfWork.BooksRepository.UpdateBook(book);
unitOfWork.BooksRepository.Save();
return RedirectToAction("Index");
}
return View();
}
public ActionResult Delete(int id)
{
Book book = unitOfWork.BooksRepository.GetBookById(id);
unitOfWork.BooksRepository.DeleteBook(book);
return View(book);
}
[HttpPost]
public ActionResult Delete(int id, FormCollection formCollection)
{
Book book = unitOfWork.BooksRepository.GetBookById(id);
unitOfWork.BooksRepository.DeleteBook(book);
unitOfWork.BooksRepository.Save();
return View("Deleted");
}
public ActionResult Deleted()
{
return View();
}
}
So from the design of our application looks something like:
Let us try to run the application so see that it is working.
Dependency Injection - Understanding how things work
Before moving ahead let us see what is happening in the controller in details.
- The
UrlRoutingModule
will parse the URL and call the controller.
- The default constructor of the Controller class will be invoked.
- This constructor will create a
UnitOfWork
class by using its default constructor.
- In the
UnitOfWork's
default constructor the BooksRepository
class will be instantiated.
- The UnitOfWork class will point to the real
BooksRepository
implementation.
- All the actions of the Controller will use the real
BooksRepository
class to perform actual CRUD operations.
What we have done in our controller is that
our controller is using UnitOfWork
class. The UnitOfWork
class contains a handle to the
interface. So we can make this to use either the BooksRepository
class or any other class
that is implementing the same interface. The We are doing Dependency injection using the
UnitOfWork
class' constructor.
Now if we need to test the application without touching the database what we can do is that we can
create a dummy class that is also implementing the IBooksRepository
and use this dummy class with our
UnitOfWork
. Which will effectively let our controller work with the dummy class.
Creating the TestProject
Now from our test project we need to perform following activities.
- Create a dummy class
DummyBooksRepository
which will implement IBooksRepository
- Create the Test functions for all the functions of our controller.
- Instantiate our dummy repository i.e.
DummyBooksRepository
.
- Create the
Unitofwork
by calling the constructor accepting a parameter of type IBooksRepository
.
We will pass our dummy repository i.e. DummyBooksRepository
in this constructor.
- The
UnitOfWork
class will now point to the DummyBooksRepository
implementation.
- Create the Controller by calling the constructor that accepts
UnitOfWork
by passing the object that we
created in previous step. This will effectively make the controller to use the DummyBooksRepository
.
Let us not try to follow the above steps and try to create our unit test project. First of all
let us create the dummy repository class implementing the IBooksRepository
interface.
class DummyBooksRepository : IBooksRepository
{
List<Book> m_books = null;
public DummyBooksRepository(List<Book> books)
{
m_books = books;
}
public List<Book> GetAllBooks()
{
return m_books;
}
public Book GetBookById(int id)
{
return m_books.SingleOrDefault(book => book.ID == id);
}
public void AddBook(Book book)
{
m_books.Add(book);
}
public void UpdateBook(Book book)
{
int id = book.ID;
Book bookToUpdate = m_books.SingleOrDefault(b => b.ID == id);
DeleteBook(bookToUpdate);
m_books.Add(book);
}
public void DeleteBook(Book book)
{
m_books.Remove(book);
}
public void Save()
{
}
}
The resulting design will look like:
Now from our test class we will instantiate this dummy repository class and create a unit of work
that will use this class. We will then instantiate the Controller that we need to test by passing this
UnitOfWork
class object into that.
[TestClass]
public class BooksControllerTest
{
Book book1 = null;
Book book2 = null;
Book book3 = null;
Book book4 = null;
Book book5 = null;
List<Book> books = null;
DummyBooksRepository booksRepo = null;
UnitOfWork uow = null;
BooksController controller = null;
public BooksControllerTest()
{
book1 = new Book { ID = 1, BookName = "test1", AuthorName = "test1", ISBN = "NA" };
book2 = new Book { ID = 2, BookName = "test2", AuthorName = "test2", ISBN = "NA" };
book3 = new Book { ID = 3, BookName = "test3", AuthorName = "test3", ISBN = "NA" };
book4 = new Book { ID = 4, BookName = "test4", AuthorName = "test4", ISBN = "NA" };
book5 = new Book { ID = 5, BookName = "test5", AuthorName = "test5", ISBN = "NA" };
books = new List<Book>
{
book1,
book2,
book3,
book4
};
booksRepo = new DummyBooksRepository(books);
uow = new UnitOfWork(booksRepo);
controller = new BooksController(uow);
}
}
Now the next step would be to write the unit tests for all the actions of the controller class.
Now since the controller is using the dummy repository, from the functionality perspective the controller
class will behave the same but will operate on our dummy repository class rather than actual repository class.
To test the Retrieval Methods of the controller let is add the following functions to the class.
[TestMethod]
public void Index()
{
ViewResult result = controller.Index() as ViewResult;
var model = (List<Book>)result.ViewData.Model;
CollectionAssert.Contains(model, book1);
CollectionAssert.Contains(model, book2);
CollectionAssert.Contains(model, book3);
CollectionAssert.Contains(model, book4);
}
[TestMethod]
public void Details()
{
ViewResult result = controller.Details(1) as ViewResult;
Assert.AreEqual(result.Model, book1);
}
Now lets add the functions to test the create, update and delete functions.
[TestMethod]
public void Create()
{
Book newBook = new Book { ID = 7, BookName = "new", AuthorName = "new", ISBN = "NA" };
controller.Create(newBook);
List<Book> books = booksRepo.GetAllBooks();
CollectionAssert.Contains(books, newBook);
}
[TestMethod]
public void Edit()
{
Book editedBook = new Book { ID = 1, BookName = "new", AuthorName = "new", ISBN = "NA" };
controller.Edit(editedBook);
List<Book> books = booksRepo.GetAllBooks();
CollectionAssert.Contains(books, editedBook);
}
[TestMethod]
public void Delete()
{
controller.Delete(1);
List<Book> books = booksRepo.GetAllBooks();
CollectionAssert.DoesNotContain(books, book1);
}
Now let us run our unit tests and see the result.
Note: The above mentioned code snippets only show the valid operations. We should also incorporate
test cases that perform invalid operations. Try to introduce some problem in the controller class and see
how the tests will fail.
Point of interest
In this article we saw how we can utilize the power of repository and Unit of Work pattern to
create unit testable ASP.NET MVC applications. We also saw how testing MVC application don't require
any web server and they can simply be tested by instantiating the Controller classes from the test project.
This article has been written from beginner'e perspective. I hope this has been informative.
History
-
17 April 2013: First version.