One of the selling points of ASP.NET MVC 2.0 was the ability to unit test your action methods.  Even though Microsoft made MVC flexible by making it easier to unit test than web forms, it isn’t always easy to implement without writing a lot of code to implement fakes for common services you use or without abstracting the interface altogether.  For instance, the HttpContextBase class is an abstract base class that you can use to abstract using the context, but you’d have to create a class that inherits from this and implement the respective methods.  As another example, you’ve probably seen controllers that implement an interface like IProductsRepository, and use an injection tool like Castle Windsor or Microsoft Unity to inject a reference into the controller’s constructor.

Enter mocking libraries, making this process easier to do.  TypeMock makes this very easy to do, and you don’t even need to write a fake or worry about dynamic injection (unless you want to, because after all, DI is pretty cool and flexible).  Let’s look at an example controller action method below:

    public class ProductsController : Controller
    {
        public ActionResult Get(int key)
        {
            var repos = new ProductsRepository();
            var product = repos.GetByKey(key);

            if (product == null)
                throw new Exception(“The product couldn’t be found”);

            return View(product);
        }

    }

Here we have a reference to the ProductsRepository; it’s a repository class serving up data from a LINQ to SQL backend.  TypeMock makes it easy to construct some tests to ensure that the action method works as expected, that it throws the error when the product is null, and that it returns the view when the product exists.

First Test – Forcing an Exception

The first test verifies that when no product returns from the database, an exception is thrown.

[Test]
public void Get_ReturningNullProductFromDBThrowsException()
{
    //Arrange
    var repos = Isolate.Fake.Instance<ProductsRepository>();
    Isolate.WhenCalled(() => repos.GetByKey(123)).WillReturn(null);
    Isolate.Swap.NextInstance<ProductsRepository>().With(repos);

    var controller = Isolate.Fake.Instance<ProductsController>(Members.CallOriginal);

    //Act
    try
    {

        var result = controller.Get(123);
       Assert.Fail();
    }
    catch (Exception ex)
    {
        Assert.AreEqual(“The product couldn’t be found”, ex.Message);
    }

    Isolate.CleanUp();
}

TypeMock’s Isolate.Fake mechanism creates a fake for the product repository, and overtakes the GetByKey method to force a null returned value.  This works great to force the result, but we need the SwapNextInstance call to ensure that the next new ProductsRepository statement returns the fake.

Second Test – Finding Product OK

The second test checks that a valid product gets served up to the view.  The way to check that is to invoke the action result of the action method, check that the result is a view result, and checks its ViewData.Model property, as illustrated below.

[Test]
public void Get_ReturningProductFromDBPassesToModel()
{
    //Arrange
    var product = new DA.Product { ProductID = 123 };
    var repos = Isolate.Fake.Instance<ProductsRepository>();
    Isolate.WhenCalled(() => repos.GetByKey(123)).WillReturn(product);
    Isolate.Swap.NextInstance<ProductsRepository>().With(repos);

    var controller = Isolate.Fake.Instance<ProductsController>(Members.CallOriginal);

    //Act
    var result = controller.Get(123);

    //Assert
    Assert.IsInstanceOf<ViewResult>(result);
    Assert.AreEqual(product, ((ViewResult)result).ViewData.Model);

    Isolate.CleanUp();
}

Instead of returning a null product, this test returns a dummy Product instance through a fake ProductsRepository. 

Third Test – Exception Bubbling

The third check tests that when an exception occurs within the product repository, that the exception bubbles up from the repository.

[Test]
public void Get_ErrorFromProviderBubblesUp()
{
    //Arrange
    var repos = Isolate.Fake.Instance<ProductsRepository>();
    Isolate.WhenCalled(() => repos.GetByKey(123)).WillThrow(new Exception(“Test”));
    Isolate.Swap.NextInstance<ProductsRepository>().With(repos);

    var controller = Isolate.Fake.Instance<ProductsController>(Members.CallOriginal);

    //Act
    try
    {
        var result = controller.Get(123);
        Assert.Fail();
    }
    catch (Exception ex)
    {
        //Assert
        Assert.AreEqual(“Test”, ex.Message);
    }

    Isolate.CleanUp();
}

Instead of returning a product, the WillThrow method simulates throwing an exception.  When calling the Get method, should throw an exception, which it does pass the test.