Click here to Skip to main content
Click here to Skip to main content

How to Write Testable Code in .NET

, 17 Nov 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
This article demonstrates how to manage dependencies to simplify unit testing of code.

Introduction

Test Driven Development is great if you know how to do it right. Unfortunately, many of the tutorials and training resources available skip right over how to write testable code because, being samples, they tend to not involve the typical layers you find in real code where you have services layers, data layers, etc. Inevitably, when you go to test your code that does have these dependencies, the tests are very slow, difficult to write, and often break as the underlying dependencies return results other than expected.

Background

Code that is well written is separated into layers, with each layer responsible for a different slice of the application. Actual layers vary based upon need and also upon developer habits, but a common scheme is:

ntier.PNG

  • User Interface/Presentation Layer: This is your presentation logic and UI interaction code.
  • Business Logic/Services Layer: This is your business logic. E.g.: code for a shopping cart. This shopping cart knows how to calculate the cart total, how to count items on the order, etc.
  • Data Access Layer/Persistence Layer: This code knows how to connect to the database and return a shopping cart, or how to save a cart to the database.
  • Database: This is where the cart's contents are saved.

Without Dependency Management

Without dependency management, when you write tests for the Presentation Layer, your code hooks into real services that hook into real data access code and then touch the real database. Really, when you are testing the "add to cart" feature or the "get cart item count", you want to test the code in isolation and be able to guarantee predictable results from your code. Without dependency management, your UI tests for "add to cart" are slow, and your dependencies return unpredictable results which can cause your test to fail.

The Solution is Dependency Injection

The solution to this problem is Dependency Injection. Dependency Injection or DI often seems confusing and complex to those who haven't done it, but in reality, it is a very, very simple concept and process with a few basic steps. What we want to do is centralize your dependencies, in this case, the use of the ShoppingCart object, and then loosely couple your code so that when you run your app, it uses real services, and when you test it, you can use fake services that are fast and dependable. Note that there are several approaches you can take; to keep it simple, I am just demonstrating constructor injection.

Step 1 - Identify Your Dependencies

Dependencies are when your code is touching other layers. E.g., when your Presentation Layer touches the Services Layer. Your presentation code depends on the services layer, but we want to test the presentation code in isolation.

public class ShoppingCartController : Controller
{
    public ActionResult GetCart()
    {
        //shopping cart service as a concrete dependency
        ShoppingCartService shoppingCartService = new ShoppingCartService();
        ShoppingCart cart = shoppingCartService.GetContents();
        return View("Cart", cart);
    }

    public ActionResult AddItemToCart(int itemId, int quantity)
    {
        //shopping cart service as a concrete dependency
        ShoppingCartService shoppingCartService = new ShoppingCartService();
        ShoppingCart cart = shoppingCartService.AddItemToCart(itemId, quantity);
        return View("Cart", cart);
    }

Step 2 - Centralize Your Dependencies

While there are several ways this can be done, in this example, I am going to create a member variable of type ShoppingCartService and then assign it to an instance that I will create in the constructor. In each place where I use ShoppingCartService, I will then re-use this instance rather than creating a new instance.

public class ShoppingCartController : Controller
{
    private ShoppingCartService _shoppingCartService;

    public ShoppingCartController()
    {
        _shoppingCartService = new ShoppingCartService();
    }
    public ActionResult GetCart()
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.GetContents();
        return View("Cart", cart);
    }

    public ActionResult AddItemToCart(int itemId, int quantity)
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity);
        return View("Cart", cart);
    }
}

Step 3 - Loose Coupling

Program against an interface rather than against concrete objects. If you write your code against IShoppingCartService as an interface rather than against the concrete ShoppingCartService, when you go to test, you can swap in a fake shopping cart service that implements IShoppingCartService. In the image below, note that the only change is that the member variable is now of type IShoppingCartService instead of just ShoppingCartService.

public interface IShoppingCartService
{
    ShoppingCart GetContents();
    ShoppingCart AddItemToCart(int itemId, int quantity);
}
public class ShoppingCartService : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        throw new NotImplementedException("Get cart from Persistence Layer");
    }
    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException("Add Item to cart then return updated cart");
    }
}
public class ShoppingCart
{
    public List<product> Items { get; set; }
}
public class Product
{
    public int ItemId { get; set; }
    public string ItemName { get; set; }
}

public class ShoppingCartController : Controller
{
    //Concrete object below points to actual service
    //private ShoppingCartService _shoppingCartService;

    //loosely coupled code below uses the interface rather than the 
    //concrete object
    private IShoppingCartService _shoppingCartService;

    public ShoppingCartController()
    {
        _shoppingCartService = new ShoppingCartService();
    }
    public ActionResult GetCart()
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.GetContents();
        return View("Cart", cart);
    }

    public ActionResult AddItemToCart(int itemId, int quantity)
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity);
        return View("Cart", cart);
    }
}

Step 4 - Inject Dependencies

We now have all of our dependencies centralized in one place, and our code is now loosely coupled to those dependencies. As with before, there are several ways to handle the next step. Without having an IoC container such as NInject or StructureMap setup, the easiest way to do this is to just overload the constructor:

//loosely coupled code below uses the interface rather 
//than the concrete object
private IShoppingCartService _shoppingCartService;

//MVC uses this constructor 
public ShoppingCartController()
{
    _shoppingCartService = new ShoppingCartService();
}

//You can use this constructor when testing to inject the    
//ShoppingCartService dependency
public ShoppingCartController(IShoppingCartService shoppingCartService)
{
    _shoppingCartService = shoppingCartService;
}

Step 5 - Test with a Stub

An example of a possible test fixture for this is below. Note that I have created a fake (a.k.a. stub) of the ShoppingCartService. This stub is passed into my Controller object, and the GetContents method is implemented to return some fake data rather than calling code that actually goes to the database. As this is 100% code, it is much faster than querying a database, and I never have to worry about staging test data or cleaning up test data when I am finished testing. Note that because of Step 2 where we centralized our dependencies, I only have to inject it once. Because of Step 3, our dependency is loosely coupled, so I can pass in any object, real or fake, as long as it implements the IShoppingCartService interface.

[TestClass]
public class ShoppingCartControllerTests
{
    [TestMethod]
    public void GetCartSmokeTest()
    {
        //arrange
        ShoppingCartController controller = 
           new ShoppingCartController(new ShoppingCartServiceStub());

        // Act
        ActionResult result = controller.GetCart();

        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }
}

/// <summary>
/// This is is a stub of the ShoppingCartService
/// </summary>
public class ShoppingCartServiceStub : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        return new ShoppingCart
        {
            Items = new List<product> {
                new Product {ItemId = 1, ItemName = "Widget"}
            }
        };
    }

    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException();
    }
}

Where do I Go From Here?

  • Use a IoC/DI container: The most common and popular IoC containers for .NET are StructureMap and Ninject. In real world code, you are going to have a lot of dependencies, your dependencies will have dependencies, etc. You will quickly find it becomes unmanageable. The answer is to use a DI/IoC framework to manage them.
  • Use an Isolation Framework: Creating stubs and mocks can get to be a lot of work, and using a mocking framework can save you a lot of time and code. The most common for .NET are Rhino Mocks and Moq.

License

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

Share

About the Author

ChrisFarrell
Software Developer Quick Solutions
United States United States
Chris Farrell is a Consultant with the Solutions Project Group of Quick Solutions, one of the largest privately held Information Technology and Solutions providers in Ohio. Chris has been developing with ASP.Net since 2002 including both on the job experience and as a core contributor for several years on the popular (150,000+ download) .Net open source RainbowPortal CMS. He is an avid supporter of ASP.Net MVC and Agile software development
Follow on   Twitter

Comments and Discussions

 
QuestionGreat job PinmemberMember 1084773630-Aug-14 11:53 
GeneralMy vote of 5 PinmemberMember 1084773630-Aug-14 11:52 
QuestionExcellent! Pinmemberdevenneym26-Mar-14 7:45 
Generalnice PinmemberPranay Rana17-Jan-11 2:04 
GeneralMy vote of 2 PinmvpPete O'Hanlon9-Nov-10 3:39 
GeneralMy vote of 4 PinmemberGraham Downs8-Nov-10 20:30 
GeneralRe: My vote of 4 PinmvpPete O'Hanlon8-Nov-10 23:17 
GeneralRe: My vote of 4 PinmemberGraham Downs8-Nov-10 23:42 
GeneralRe: My vote of 4 PinmemberS. Senthil Kumar9-Nov-10 2:39 
GeneralMy vote of 4 PinmemberMember 33380138-Nov-10 18:21 
GeneralMy vote of 5 PinmemberAxim8-Nov-10 13:26 
GeneralNot bad. PinmvpPete O'Hanlon7-Nov-10 9:37 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 17 Nov 2010
Article Copyright 2010 by ChrisFarrell
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid