Click here to Skip to main content
15,860,859 members
Articles / Web Development / ASP.NET
Article

Creating Unit Testable Applications in ASP.NET MVC - A Beginner's Tutorial

Rate me:
Please Sign up or sign in to vote.
4.94/5 (28 votes)
16 Apr 2013CPOL6 min read 116.1K   3.3K   78   10
In this article we will talk about creating unit testable applications using ASP.NET MVC.

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.

Image 1

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.

Image 2

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.

C#
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.

C#
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:

C#
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.

C#
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:

C#
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:

Image 3

Let us try to run the application so see that it is working.

Image 4

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 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.

  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.

C#
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: 

Image 5

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.

C#
[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.

C#
[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.

C#
[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.

Image 6

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India

I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems. IMO, C# is the best programming language and I love working with C# and other Microsoft Technologies.

  • Microsoft Certified Technology Specialist (MCTS): Web Applications Development with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Accessing Data with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Windows Communication Foundation Development with Microsoft .NET Framework 4

If you like my articles, please visit my website for more: www.rahulrajatsingh.com[^]

  • Microsoft MVP 2015

Comments and Discussions

 
Questionerror Pin
Member 1066102723-Nov-16 22:34
Member 1066102723-Nov-16 22:34 
QuestionUnit testing using the Generic Unit of Work and Generic Repository Pin
ioranet20-Apr-16 9:22
ioranet20-Apr-16 9:22 
QuestionThanks Rahul Pin
Praveen Raju25-Jun-15 6:42
professionalPraveen Raju25-Jun-15 6:42 
GeneralMy vote of 3 Pin
Marcio Wesley Borges11-Feb-15 5:41
professionalMarcio Wesley Borges11-Feb-15 5:41 
QuestionAuto Generate UnitOfWork and RepositoryClasses Pin
Mohammed Sadullah17-Dec-14 19:42
Mohammed Sadullah17-Dec-14 19:42 
QuestionIs it really Unit Testing? Pin
Iman-mahmoudinasab3-Dec-14 5:40
Iman-mahmoudinasab3-Dec-14 5:40 
QuestionCool article Pin
Yishi15-Apr-14 22:49
Yishi15-Apr-14 22:49 
AnswerArticle of the Day on Microsoft's site Pin
Rahul Rajat Singh12-Aug-13 17:18
professionalRahul Rajat Singh12-Aug-13 17:18 
QuestionMockign Framework Pin
Thanigainathan.S23-Apr-13 20:38
Thanigainathan.S23-Apr-13 20:38 
GeneralMy vote of 5 Pin
Abinash Bishoyi17-Apr-13 3:43
Abinash Bishoyi17-Apr-13 3:43 
Nice

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.