Thursday, September 16, 2010

Unit Tests for ASP.NET MVC application that uses resources in code behind.

Download source files - here.

Some time ago I start to play with ASP.NET MVC. It is very well pattern and Microsoft made nice choose that implemented it for ASP.NET in my opinion.
I started a project that uses ASP.NET MVC. This project requests multilingual support. I use resources for this. It works well when I use resource's properties in the View's layer but when I call resource's properties from code behind. It's something likes to next:
public ActionResult Index()
{
ViewData["Message"] = CommonResource.WELCOME;
return View();
}
It works but I pay attention that this code breaks my Unit Tests. My Unit Tests looks like:
[TestMethod]
public void Index_Test()
{
//// Arrange
var controller = new HomeController();

// Act
var result = controller.Index();

// Assert
Assert.IsInstanceOfType(result, typeof(ViewResult));
}
It happens because I run my code in other project and my ResourceManager is not filled (it equals to null). How can I fix it? A first idea that came to mind is "extract interface".. but it is very annoying because resource's class is auto-generate and I should update this code after each updating of resources.. I made some research and found additional way for extracting resources - HttpContext contains GetGlobalResourceObject method! It is well way because I can set mock for HttpContext and for any its methods or properties.

Note: GetGlobalResourceObject returns object but I use only strings for localization. So I create linq method for HttpContext that returns string if it presents:
public static string GetGlobalResource(
this HttpContextBase httpContext
, string classKey, string resourceKey)
{
var result = httpContext.GetGlobalResourceObject
(classKey, resourceKey);
return null != result
? result.ToString() : string.Empty;
}
Pay attention, MissingManifestResourceException is possible in this code if you pass incorrect key/keys!

So I change pieces of code in Controller's classes in the next manner:
public ActionResult Index()
{
ViewData[CommonStrings.MESSAGE] = HttpContext
.GetGlobalResource(CommonStrings
.COMMON_RESOURCE, CommonStrings.WELCOME);
return View();
}
After it I should change my Unit Test's methods in next manner:
[TestMethod]
public void Index_Test()
{
//// Arrange
var httpContextMock = MockRepository
.GenerateMock<HttpContextBase>();
httpContextMock.Stub
(x => x.GetGlobalResource(
CommonStrings.COMMON_RESOURCE, CommonStrings.WELCOME))
.Return(CommonStrings.WELCOME);

var controller = new HomeController();
var context = new ControllerContext
(httpContextMock, new RouteData(), controller);
controller.ControllerContext = context;

// Act
var result = controller.Index();

// Assert
Assert.IsInstanceOfType(result, typeof(ViewResult));
ViewDataDictionary viewData = ((ViewResult)result).ViewData;
Assert.AreEqual(CommonStrings.WELCOME
, viewData[CommonStrings.MESSAGE]);
}
Moreover, you can see that I added additional checking related to localization.

1 comment:

ulu said...

You can also take a look at Ivonna -- Asp.Net (WebForms and MVC) integration and unit testing tool: http://sm-art.biz/Ivonna.aspx