Skip to content

Instantly share code, notes, and snippets.

@johnnyreilly
Last active October 29, 2019 13:43
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save johnnyreilly/4959924 to your computer and use it in GitHub Desktop.
Save johnnyreilly/4959924 to your computer and use it in GitHub Desktop.
What you need to unit test MVC controllers using MOQ.
using System.Web.Mvc;
namespace DemoApp.Areas.Demo
{
public class DemoAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "DemoArea";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"DemoArea_default",
"Demo/{oneTypeOfId}/{anotherTypeOfId}/{controller}/{action}/{id}",
new { oneTypeOfId = 0, anotherTypeOfId = 0, action = "Index", id = UrlParameter.Optional }
);
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace DemoApp.Areas.Demo.Controllers
{
public class DemoController : System.Web.Mvc.Controller
{
//....
public JsonResult Edit(AnObject anObject)
{
//Indicate to the client we have saved and pass back the redirect URL
return Json(new {
Saved = true,
RedirectUrl = Url.Action("Details", anObject.AnotherTypeOfId)
});
}
//....
}
}
using Moq;
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UnitTest.TestUtilities
{
/// <summary>
/// This class of MVC Mock helpers is originally based on Scott Hanselman's 2008 post:
/// http://www.hanselman.com/blog/ASPNETMVCSessionAtMix08TDDAndMvcMockHelpers.aspx
///
/// This has been updated and tweaked to work with MVC 3 / 4 projects (it hasn't been tested with MVC
/// 1 / 2 but may work there) and also based my use cases
/// </summary>
public static class MvcMockHelpers
{
#region Mock HttpContext factories
public static HttpContextBase MockHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
request.Setup(r => r.AppRelativeCurrentExecutionFilePath).Returns("/");
request.Setup(r => r.ApplicationPath).Returns("/");
response.Setup(s => s.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(s => s);
response.SetupProperty(res => res.StatusCode, (int)System.Net.HttpStatusCode.OK);
context.Setup(h => h.Request).Returns(request.Object);
context.Setup(h => h.Response).Returns(response.Object);
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);
return context.Object;
}
public static HttpContextBase MockHttpContext(string url)
{
var context = MockHttpContext();
context.Request.SetupRequestUrl(url);
return context;
}
#endregion
#region Extension methods
public static void SetMockControllerContext(this Controller controller,
HttpContextBase httpContext = null,
RouteData routeData = null,
RouteCollection routes = null)
{
//If values not passed then initialise
routeData = routeData ?? new RouteData();
routes = routes ?? RouteTable.Routes;
httpContext = httpContext ?? MockHttpContext();
var requestContext = new RequestContext(httpContext, routeData);
var context = new ControllerContext(requestContext, controller);
//Modify controller
controller.Url = new UrlHelper(requestContext, routes);
controller.ControllerContext = context;
}
public static void SetHttpMethodResult(this HttpRequestBase request, string httpMethod)
{
Mock.Get(request).Setup(req => req.HttpMethod).Returns(httpMethod);
}
public static void SetupRequestUrl(this HttpRequestBase request, string url)
{
if (url == null)
throw new ArgumentNullException("url");
if (!url.StartsWith("~/"))
throw new ArgumentException("Sorry, we expect a virtual url starting with \"~/\".");
var mock = Mock.Get(request);
mock.Setup(req => req.QueryString).Returns(GetQueryStringParameters(url));
mock.Setup(req => req.AppRelativeCurrentExecutionFilePath).Returns(GetUrlFileName(url));
mock.Setup(req => req.PathInfo).Returns(string.Empty);
}
/// <summary>
/// Facilitates unit testing of anonymouse types - taken from here:
/// http://stackoverflow.com/a/5012105/761388
/// </summary>
public static object GetReflectedProperty(this object obj, string propertyName)
{
obj.ThrowIfNull("obj");
propertyName.ThrowIfNull("propertyName");
var property = obj.GetType().GetProperty(propertyName);
if (property == null)
return null;
return property.GetValue(obj, null);
}
public static T ThrowIfNull<T>(this T value, string variableName) where T : class
{
if (value == null)
throw new NullReferenceException(
string.Format("Value is Null: {0}", variableName));
return value;
}
#endregion
#region Private
static string GetUrlFileName(string url)
{
return (url.Contains("?"))
? url.Substring(0, url.IndexOf("?"))
: url;
}
static NameValueCollection GetQueryStringParameters(string url)
{
if (url.Contains("?"))
{
var parameters = new NameValueCollection();
var parts = url.Split("?".ToCharArray());
var keys = parts[1].Split("&".ToCharArray());
foreach (var key in keys)
{
var part = key.Split("=".ToCharArray());
parameters.Add(part[0], part[1]);
}
return parameters;
}
return null;
}
#endregion
}
}
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace UnitTest.Areas.Demo.Controllers
{
[TestClass]
public class UnitTestingAnAreaUsingUrlHelper
{
private DemoController _controller;
[TestInitialize]
public void InitializeTest()
{
_controller = new DemoController();
}
[TestMethod]
public void Edit_updates_the_object_and_returns_a_JsonResult_containing_the_redirect_URL()
{
// Arrange
int anotherTypeOfId = 5332;
//Register the area as well as standard routes
RouteTable.Routes.Clear();
var areaRegistration = new DemoAreaRegistration();
var areaRegistrationContext = new AreaRegistrationContext(
areaRegistration.AreaName, RouteTable.Routes);
areaRegistration.RegisterArea(areaRegistrationContext);
RouteConfig.RegisterRoutes(RouteTable.Routes);
//Initialise the controller and setup the context so MVC can pick up the relevant route data
var httpContext = MvcMockHelpers.MockHttpContext(
"~/Demo/77969/" + anotherTypeOfId + "/Company/Edit");
var routeData = RouteTable.Routes.GetRouteData(httpContext);
_controller.SetMockControllerContext(
httpContext, routeData, RouteTable.Routes);
// Act
var result = _controller.Edit(
new AnObject{
WithAProperty = "Something",
AnotherTypeOfId = anotherTypeOfId });
// Assert
Assert.AreEqual("DemoArea", areaRegistration.AreaName);
Assert.IsInstanceOfType(result, typeof(JsonResult));
Assert.IsNotNull(result.Data,
"There should be some data for the JsonResult");
Assert.AreEqual(true,
result.Data.GetReflectedProperty("Saved"));
Assert.AreEqual("/Demo/77969/" + anotherTypeOfId + "/Company/Details",
result.Data.GetReflectedProperty("RedirectUrl"));
}
}
}
@RobBlob
Copy link

RobBlob commented Oct 4, 2017

Very helpful.

Minor comment - in MvcMockHelpers.cs, lines 35-36 and 38-39 are redundant.

@RobBlob
Copy link

RobBlob commented Oct 4, 2017

Further, the ThrowIfNull should not be throwing a NullReferenceException - see the following: https://stackoverflow.com/questions/22453650/why-are-we-not-to-throw-these-exceptions

Prefer an ArgumentNullException, or just let the system throw the NullReferenceException.

@propellingbits
Copy link

Mocking these http objects (request, response etc) is an overkill. We should unit test business components\services. Testing of controllers can be covered via a better approach - Integration Testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment