Vous êtes sur la page 1sur 12

Creating Unit Testable Applications in ASP.

NET
MVC - A Beginner's Tutorial
Rahul Rajat Singh, 16 Apr 2013

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;

// This will be called from controller default constructor


public UnitOfWork()
{
entities = new SampleDatabaseEntities();
BooksRepository = new BooksRepository(entities);
}

// This will be created from test project and passed on to the


// controllers parameterized constructors
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.

1. The UrlRoutingModule will parse the URL and call the controller.
2. The default constructor of the Controller class will be invoked.
3. This constructor will create a UnitOfWork class by using its default constructor.
4. In the UnitOfWork's default constructor the BooksRepository class will be instantiated.
5. The UnitOfWork class will point to the real BooksRepository implementation.
6. 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 theUnitOfWork 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.

1. Create a dummy class DummyBooksRepository which will implement IBooksRepository


2. Create the Test functions for all the functions of our controller.
3. Instantiate our dummy repository i.e. DummyBooksRepository.
4. 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.
5. The UnitOfWork class will now point to the DummyBooksRepository implementation.
6. 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


{
// Master list of books that will mimic the persitent database storage
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()


{
// Nothing to do here
}
}
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()
{
// Lets create some sample books
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
};

// Lets create our dummy repository


booksRepo = new DummyBooksRepository(books);

// Let us now create the Unit of work with our dummy repository
uow = new UnitOfWork(booksRepo);

// Now lets create the BooksController object to test and pass our unit of work
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()
{
// Lets call the action method now
ViewResult result = controller.Index() as ViewResult;

// Now lets evrify whether the result contains our book entries or not
var model = (List<Book>)result.ViewData.Model;

CollectionAssert.Contains(model, book1);
CollectionAssert.Contains(model, book2);
CollectionAssert.Contains(model, book3);
CollectionAssert.Contains(model, book4);

// Uncomment the below line and the test will start failing
// CollectionAssert.Contains(model, book5);
}

[TestMethod]
public void Details()
{
// Lets call the action method now
ViewResult result = controller.Details(1) as ViewResult;

// Now lets evrify whether the result contains our book


Assert.AreEqual(result.Model, book1);
}
Now lets add the functions to test the create, update and delete functions.

[TestMethod]
public void Create()
{
// Lets create a valid book objct to add into
Book newBook = new Book { ID = 7, BookName = "new", AuthorName = "new", ISBN = "NA" };

// Lets call the action method now


controller.Create(newBook);

// get the list of books


List<Book> books = booksRepo.GetAllBooks();

CollectionAssert.Contains(books, newBook);
}

[TestMethod]
public void Edit()
{
// Lets create a valid book objct to add into
Book editedBook = new Book { ID = 1, BookName = "new", AuthorName = "new", ISBN = "NA" };

// Lets call the action method now


controller.Edit(editedBook);

// get the list of books


List<Book> books = booksRepo.GetAllBooks();

CollectionAssert.Contains(books, editedBook);
}

[TestMethod]
public void Delete()
{
// Lets call the action method now
controller.Delete(1);

// get the list of books


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.

Vous aimerez peut-être aussi