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()
{
}
[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 = if (user != null)
{
return Redirect(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
[HttpPost]
public ActionResult LogOff()
{
return RedirectToAction("Index", "Home");
}
}
Let's start writing integration tests.
- The first Login action is accessible by anonymous users and the
ViewBag.ReturnUrl
is set.
[Test]
public void GetLoginTest()
{
var action = new AccountController().Action(c => c.Login("[returnUrl]"));
var result = action.Execute();
Assert.IsInstanceOf<ViewResult>(result.ActionResult);
Assert.AreEqual("[returnUrl]", result.ViewBag.ReturnUrl);
}
- The second Login action is accessible by anonymous users with post method and requires
LoginViewModel
to be valid.
[Test]
public void InvalidPostLoginTest()
{
var action = new AccountController().Action
(c => c.Login(null), "GET" );
Assert.IsNull(action); }
[Test]
public void PostLoginTest()
{
var model = new LoginViewModel
{ UserName = "user1", Password = "passw1" };
var action = new AccountController().Action(c => c.Login(model), "POST");
var result = action.Execute();
Assert.IsInstanceOf<RedirectResult>(result.ActionResult);
Assert.AreEqual("[returnUrl]", result.ViewBag.ReturnUrl);
}
- The logoff action is accessible by authorized users only with
post
method.
[Test]
public void PostLogOffTest()
{
var action = new AccountController().Action(c => c.LogOff(), "POST");
var result = action.Authenticate("user1", new string[0]).Execute();
Assert.IsInstanceOf<RedirectToRouteResult>(result.ActionResult);
}
[Test]
public void PostLogOffUnauthenticatedTest()
{
var action = new AccountController().Action(c => c.LogOff(), "POST");
var result = action.Execute();
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
.