-
-
Save johnnyreilly/4959924 to your computer and use it in GitHub Desktop.
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")); | |
} | |
} | |
} |
Thanks, I found this really handy!
I used this but updated MvcMockHelpers.cs to use a System.Uri object in SetupRequestUrl() instead of a relative url string so that I can populate HttpContextBase.Request.Url, I pass a Uri object in instead of the relative url allowing access to the Request.Url properties in the controller such as Request.Url.Scheme or Request.Url.Authority etc.
MvcMockHelpers.cs
public static void SetupRequestUrl(this HttpRequestBase request, Uri uri)
{
if (uri == null)
throw new ArgumentNullException("url");
var mock = Mock.Get(request);
//removed exception and replaced it by using local path prefixed with ~
var localPath = "~" + uri.LocalPath;
mock.Setup(req => req.QueryString).Returns(GetQueryStringParameters(localPath));
mock.Setup(req => req.AppRelativeCurrentExecutionFilePath).Returns(GetUrlFileName(localPath));
mock.Setup(req => req.PathInfo).Returns(string.Empty);
mock.Setup(req => req.Path).Returns(uri.LocalPath);
//setup the uri object for the request context
mock.Setup(req => req.Url).Returns(uri);
}
The calling functions such as MockHttpContext() also needed updating to pass the uri through.
Then my unit test setup all I do is create a uri object and pass it through;
[TestInitialize]
public void setup()
{
..
var uri = new Uri("http://mydummyhost.local/urlpath/);
..
context = MvcMockHelpers.MockHttpContext(uri);
..
}
Very helpful.
Minor comment - in MvcMockHelpers.cs, lines 35-36 and 38-39 are redundant.
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.
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.
Thanks for putting this altogether! Very handy, usable and useful!
Just a minor thing - seems like in MvcMockHelpers.cs, lines #35-39 are duplicates (unless I'm missing something).
Appreciate for sharing it!