views:

56

answers:

1

I am having an issue where UrlHelper's RouteUrl method only returns an empty string when run in my tests, though function properly when executing in the real HttpContext. It is, however, finding the route - as I do properly get an exception if I try to resolve a route name which has not been defined.

I have mocked the HttpContext and friends using the code provided by Scott Hanselman/Kzu and added the code needed to bootstrap the Application's Routes into the mocked instance

To reduce the number of variables in my situation, I've written a simple test:

[Test]
public void UrlHelperReturnsCorrectUrl()
{
  var controller = new MyController();
  controller.SetFakeControllerContext().LoadUrlHelper();

  Assert.AreEqual("My/Route/Path", controller.Url.RouteUrl("MyRoute"));
}

Interestingly enough, accessing the RouteCollection directly and using VirtualPath does work:

[Test]
public void GetVirtualPathReturnsCorrectUrl()
{
    var controller = new AccountController();
    controller.SetFakeControllerContext().LoadUrlHelper();
    Assert.AreEqual("My/Route/Path", 
               Controller.Url.RouteCollection["MyRoute"]
               .GetVirtualPath(
                   controller.Url.RequestContext,
                   new RouteValueDictionary())
               .VirtualPath);
}

For reference, Here is my implementation of the LoadUrlHelper extension method:

public static Controller LoadUrlHelper(this Controller controller)
{
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    controller.Url = new UrlHelper(
                        controller.ControllerContext.RequestContext, 
                        routes);
    return controller;
}

And here is my route as defined in my application's Global.asax:

routes.MapRoute(
   "MyRoute", "My/Route/Path",
   new {controller = "Home", action = "Index"});

Has anyone run into this? Am I missing something?

EDIT:

I've followed the MVC code down to the point that it hands the route processing off to System.Routing and found something very interesting. The code that MVC eventually runs to lookup the desired URL (condensed, of course) returns an empty string:

Controller.Url.RouteCollection.GetVirtualPath(
          Controller.Url.RequestContext, 
          "MyRoute", new RouteValueDictionary()).VirtualPath;

whereas an extremely similar variant returns the expected string:

Controller.Url.RouteCollection["MyRoute"].GetVirtualPath(
          Controller.Url.RequestContext, 
          new RouteValueDictionary()).VirtualPath;

I can't seem to go any further in the underlying code to see what is actually happening differently here, but thought it might help someone understand what setup I am missing. (I'm not going to yell bug yet, as the fact stands that the UrlHelpers do work when in a real HttpContext)

A: 

The solution to my problem was already posted to another SO question.

I had tried to incorporate this solution piece-meal earlier but did so poorly. Once I copied it entirely and modified for my situation, it worked perfectly.

Here is a more generic version that can be reused across many tests (if placed in a base test fixture class or something similar).

Usage:

var controller = GetController<MyController>();
controller.MyAction();
//...

Method:

protected T GetController<T>() where T : Controller, new()
{
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
    request.SetupGet(x => x.ApplicationPath).Returns("/");
    request.SetupGet(x => x.Url).Returns(new Uri("http://localhost", UriKind.Absolute));
    request.SetupGet(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection());

    var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
    response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string p) => p);

    var context = new Mock<HttpContextBase>(MockBehavior.Strict);
    context.SetupGet(x => x.Request).Returns(request.Object);
    context.SetupGet(x => x.Response).Returns(response.Object);

    var controller = new T();
    controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
    controller.Url = new UrlHelper(new RequestContext(context.Object, new RouteData()), routes);

    return controller;
}
James Maroney