Как заставить модульные тесты использовать маршруты в ASP.NET MVC?

Я пишу модульные тесты для своего приложения ASP.NET MVC, в частности, я тестирую написанный мной метод расширения HtmlHelper. Внутри метода расширения есть строка:

var innerHtml = htmlHelper.ActionLink(text, action, controller, routeValues, null);

Когда я запускаю это внутри своего модульного теста, href сгенерированного URL-адреса остается пустым независимо от переданного действия или контроллера.

Вот мой модульный тест:

var page = CreateProductDataPage(); //returns ProductDataPage object
var htmlHelper = Http.CreateHtmlHelperWithMocks<ProductDataPage>(new ViewDataDictionary<ProductDataPage>(page), false);
var result = htmlHelper.ProductListingBreadcrumb(true, null, null);

Вот метод CreateHtmlHelperWithMocks:

public static HtmlHelper<T> CreateHtmlHelperWithMocks<T>(ViewDataDictionary<T> viewData, bool isLoggedIn) where T : class
{
    var mockViewDataContainer = new Mock<IViewDataContainer>();
    mockViewDataContainer.SetupGet(v => v.ViewData).Returns(viewData);

    return new HtmlHelper<T>(GetViewContextMock(viewData, isLoggedIn).Object, mockViewDataContainer.Object);
}

Наконец, вот метод GetViewContextMock:

public static Mock<ViewContext> GetViewContextMock(ViewDataDictionary viewData, bool isLoggedIn)
{
    var mock = new Mock<ViewContext>();

    mock.SetupGet(v => v.HttpContext).Returns(GetHttpContextMock(isLoggedIn).Object);
    mock.SetupGet(v => v.Controller).Returns(new Mock<ControllerBase>().Object);
    mock.SetupGet(v => v.View).Returns(new Mock<IView>().Object);
    mock.SetupGet(v => v.ViewData).Returns(viewData);
    mock.SetupGet(v => v.TempData).Returns(new TempDataDictionary());
    mock.SetupGet(v => v.RouteData).Returns(new RouteData());

    return mock;
}

person mkedobbs    schedule 06.12.2009    source источник


Ответы (2)


Обновление: разобрался. Какая боль в $$. В случае, если кто-то еще попытается сделать это...

Первым шагом было добавление коллекции маршрутов из global.asax при создании макета HtmlHelper.

    public static HtmlHelper<T> CreateHtmlHelperWithMocks<T>(ViewDataDictionary<T> viewData, bool isLoggedIn) where T : class
    {
        var mockViewDataContainer = new Mock<IViewDataContainer>();
        mockViewDataContainer.SetupGet(v => v.ViewData).Returns(viewData);

        //These next two lines are key:
        var routeCollection = new RouteCollection();
        MvcApplication.RegisterRoutes(routeCollection);

        return new HtmlHelper<T>(GetViewContextMock(viewData, isLoggedIn).Object, mockViewDataContainer.Object, routeCollection);
    }

Затем мне нужно было убедиться, что макет HttpContext имеет результат, возвращаемый для свойства ApplicationPath запроса и метода ApplyAppPathModifier ответа.

    public static Mock<HttpContextBase> GetHttpContextMock(bool isLoggedIn)
    {
        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>();
        var principal = AuthenticationAndAuthorization.GetPrincipleMock(isLoggedIn);

        //These next two lines are required for the routing to generate valid URLs, apparently:
        request.SetupGet(r => r.ApplicationPath).Returns("/");
        response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns((string r) => r);

        context.SetupGet(c => c.Request).Returns(request.Object);
        context.SetupGet(c => c.Response).Returns(response.Object);
        context.SetupGet(c => c.Session).Returns(session.Object);
        context.SetupGet(c => c.Server).Returns(server.Object);
        context.SetupGet(c => c.User).Returns(principal.Object);

        return context;
    }
person mkedobbs    schedule 06.12.2009
comment
Большое спасибо! Вы спасли мой день/вечер/ночь. Согласна, какая боль. Да, они представили интерфейсы и базовые классы, открытые для насмешек, но это не значит, что они упростили задачу. - person Thomas Eyde; 10.10.2010

Я написал в блоге об этом с Rhino.Mocks около месяца назад. Дополнительную информацию о том, как я справляюсь с этим, можно найти на странице http://farm-fresh-code.blogspot.com/2009/10/mocking-htmlhelper-class-with.html. По сути, мое решение состоит в том, чтобы предоставить все в макете, как в RouteData, так и через ApplyAppPathModifier в объекте Response, подключенном к помощнику макета. На самом деле это скорее поддельный помощник, основанный на базовых заглушках.

person tvanfosson    schedule 06.12.2009
comment
Я думаю, что точка, в которой вы сдались в этом посте, - это именно та точка, которую я пытаюсь преодолеть прямо сейчас... Я смотрел на источник MVC, потому что они непосредственно тестируют ActionLink там, но я не могу понять, что кусок мне не хватает. - person mkedobbs; 07.12.2009