Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET MVC End-to-End Integration Testing

0.00/5 (No votes)
5 Dec 2014 2  
ASP.NET MVC application are highly testable when controllers are considered plain classes, but then you lose the integration with model validation, filters, method selectors, etc.

Introduction

This tip describes how the Xania.AspNet.Simulator library helps with integration testing of MVC application, in a way that makes it enjoyable. No complex boilerplate code is needed anymore to test your controller action, while providing support for action filters, model validation, action selectors and more.

Background

I've been working on many different ASP.NET MVC applications and, despite all good initial intentions, most of these projects were not built with testability in mind. In my last project, I was explicitly tasked with improving code quality through refactoring code, in short, replacing static classes with services, removing duplicate code block, splitting up methods into smaller pieces with less responsibility, etc.

I found myself in a situation where nothing is like it seems. The code base is nothing near "Self-documenting", even the 'Login' action is much more than just simply logging in the user.

The solution to this was starting from the top, at the action level, where I had control of the request, response, headers, session, cookies, routing, filters, selectors, viewdata and modelstate and write tests at that level before starting refactoring the code. Unfortunately, ASP.NET MVC framework does not support an easy way to write tests for controller actions that support all these features by default. So, I started coding to bundle all in an easy to use package named Xania.AspNet.Simulator.

Using the Code

Take for example the following account controller implementation:

[Authorize]
public class AccountController : System.Web.Mvc.Controller
{
    public AccountController()
    {
    }

    //
    // GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    [HttpPost]
    [AllowAnonymous]
    public ActionResult Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = // find user
            if (user != null)
            {
                return Redirect(returnUrl);
            }
            else
            {
                ModelState.AddModelError("", "Invalid username or password.");
            }
        }
         // If we got this far, something failed, redisplay form
        return View(model);
    }

    //
    // POST: /Account/LogOff
    [HttpPost]
    public ActionResult LogOff()
    {
        // sign out user
        return RedirectToAction("Index", "Home");
    }
}

Let's start writing integration tests.

  1. The first Login action is accessible by anonymous users and the ViewBag.ReturnUrl is set.
    [Test]
    public void GetLoginTest() 
    {
        // arrange
        var action = new AccountController().Action(c => c.Login("[returnUrl]"));
        // act
        var result = action.Execute();
        // assert
        Assert.IsInstanceOf<ViewResult>(result.ActionResult);
        Assert.AreEqual("[returnUrl]", result.ViewBag.ReturnUrl);
    }
  2. The second Login action is accessible by anonymous users with post method and requires LoginViewModel to be valid.
    [Test]
    public void InvalidPostLoginTest() 
    {
        // arrange, when http method is GET
        var action = new AccountController().Action
        (c => c.Login(null), "GET" /* is default */);
    
        // assert
        Assert.IsNull(action); // not found
    }
    
    [Test]
    public void PostLoginTest() 
    {
        // arrange
        var model = new LoginViewModel 
        { UserName = "user1", Password = "passw1" };
        var action = new AccountController().Action(c => c.Login(model), "POST");
        // act
        var result = action.Execute();
        // assert, assume user exists
        Assert.IsInstanceOf<RedirectResult>(result.ActionResult);
        Assert.AreEqual("[returnUrl]", result.ViewBag.ReturnUrl);
    }
  3. The logoff action is accessible by authorized users only with post method.
    [Test]
    public void PostLogOffTest() 
    {
        // arrange
        var action = new AccountController().Action(c => c.LogOff(), "POST");
        // act
        var result = action.Authenticate("user1", new string[0]).Execute();
        // assert
        Assert.IsInstanceOf<RedirectToRouteResult>(result.ActionResult);
    }
    
    [Test]
    public void PostLogOffUnauthenticatedTest() 
    {
        // arrange
        var action = new AccountController().Action(c => c.LogOff(), "POST");
        // act, but don't authenticate
        var result = action.Execute();
        // assert
        Assert.IsInstanceOf<HttpUnauthorizedResult>(result.ActionResult);
    }

Points of Interest

The code base is on github:

https://github.com/ibrahimbensalah/Xania.AspNet.Simulator

And a distribution can be found at NuGet with Id Xania.AspNet.Simulator.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here