Mocking Controller.Url.Action(строка, строка, объект, строка) в ASP.NET MVC

Я использую NUnit и Moq библиотеки для модульного тестирования. Мне нужно высмеять перегруженный Url.Action(строка, строка, объект, строка), потому что мое действие контроллера использует его для получить абсолютный URL-адрес действия.

Теперь попробую (посмотрите тест MockUrlAction):

[TestFixture]
public class ControllerTests   
{
    [Test]
    public void MockUrlAction()
    {
        var controller = new TestController();

        controller.Url = new UrlHelper(new RequestContext(MvcMoqHelpers.FakeHttpContext(), new RouteData()), GetRouteCollection());

        // it works
        Assert.AreEqual("/PathToAction", controller.Url.Action("TestAction")); 

        // but it doesn't work
        Assert.AreEqual("http://example.com/PathToAction", controller.Url.Action("TestAction", null, null, "http")); 
    }

    private RouteCollection GetRouteCollection()
    {
        BundleTable.MapPathMethod = MapBundleItemPath;
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        var adminArea = new AdminAreaRegistration();
        var adminAreaRegistrationContext = new AreaRegistrationContext(adminArea.AreaName, routes);
        adminArea.RegisterArea(adminAreaRegistrationContext);

        return routes;
    }
}

public static class MvcMoqHelpers
{
    public static HttpContextBase FakeHttpContext()
    {
        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);

        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;
    }
}

И на линии

Assert.AreEqual("http://example.com/PathToAction", controller.Url.Action("TestAction", null, null, "http"));

Я получаю исключение

System.NullReferenceException : Object reference not set to an instance of an object.
at System.Web.Mvc.UrlHelper.GenerateUrl(String routeName, String actionName, String controllerName, String protocol, String hostName, String fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, Boolean includeImplicitMvcValues)
at System.Web.Mvc.UrlHelper.Action(String actionName, String controllerName, Object routeValues, String protocol)

Странно, что controller.Url.Action("TestAction") работает нормально, но controller.Url.Action("TestAction", null, null, "http") не работает.

P.S. MvcMoqHelpers.FakeHttpContext() из здесь, возможно, это поможет ответить на вопрос.

Итак, вопрос: как я могу получить Url.Action(строка, строка, объект, строка)?

Спасибо.

Ответ 1

Вы должны установить Request.Url, и у вас есть этот фрагмент кода в учебнике, который вы указали:

public static HttpContextBase FakeHttpContext(string url)
{
    HttpContextBase context = FakeHttpContext();
    context.Request.SetupRequestUrl(url);
    return context;
}

Причина - в вашей перегрузке Url.Action вы не указываете имя хоста и протокол, поэтому MVC пытается извлечь эти значения из Request.Url

if (!String.IsNullOrEmpty(protocol) || !String.IsNullOrEmpty(hostName))
{
    Uri requestUrl = requestContext.HttpContext.Request.Url;
    protocol = (!String.IsNullOrEmpty(protocol)) ? protocol : Uri.UriSchemeHttp;
    hostName = (!String.IsNullOrEmpty(hostName)) ? hostName : requestUrl.Host;