Last active
October 29, 2019 13:43
-
-
Save johnnyreilly/4959924 to your computer and use it in GitHub Desktop.
What you need to unit test MVC controllers using MOQ.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | |
); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
}); | |
} | |
//.... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.