Click here to Skip to main content
14,922,948 members
Articles / DevOps / Testing
Tip/Trick
Posted 5 Dec 2014

Stats

27.1K views
8 bookmarked

ASP.NET MVC End-to-End Integration Testing

Rate me:
Please Sign up or sign in to vote.
4.50/5 (3 votes)
5 Dec 2014CPOL2 min read
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:

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

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

License

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

Share

About the Author

Ibrahim ben Salah
Netherlands Netherlands
No Biography provided

Comments and Discussions

 
QuestionIs this a Controller mocking feature? Pin
mwpowellhtx2-Jan-17 12:27
Membermwpowellhtx2-Jan-17 12:27 
Hello,

Please help me understand, is this yet another Controller mocking feature?

I am looking for a full, in-process, ASP.NET MVC self-hosting/server. Does this do that as well?

Thank you...
GeneralRe: Is this a Controller mocking feature? Pin
mwpowellhtx2-Jan-17 12:30
Membermwpowellhtx2-Jan-17 12:30 
GeneralRe: Is this a Controller mocking feature? Pin
Ibrahim ben Salah27-Feb-17 22:26
MemberIbrahim ben Salah27-Feb-17 22:26 
AnswerRe: Is this a Controller mocking feature? Pin
Ibrahim ben Salah27-Feb-17 22:13
MemberIbrahim ben Salah27-Feb-17 22:13 
QuestionHow would I call an Action that has parameters; eg. new MyController().Action(c => c.Delete(1, 1)) Pin
Heistrouble4-Mar-15 9:30
MemberHeistrouble4-Mar-15 9:30 
AnswerRe: How would I call an Action that has parameters; eg. new MyController().Action(c => c.Delete(1, 1)) Pin
Ibrahim ben Salah5-Mar-15 12:27
MemberIbrahim ben Salah5-Mar-15 12:27 
AnswerRe: How would I call an Action that has parameters; eg. new MyController().Action(c => c.Delete(1, 1)) Pin
Ibrahim ben Salah6-Mar-15 22:23
MemberIbrahim ben Salah6-Mar-15 22:23 
GeneralMy vote of 3 Pin
visu_19-Feb-15 13:53
Membervisu_19-Feb-15 13:53 

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.