Тестирование блока контроллера ASP.NET MVC - проблема с расширением UrlHelper

Попытка выполнить блок-тестирование контроллера в моем веб-приложении ASP.NET MVC 3.

Мой тест выглядит следующим образом:

[TestMethod]
public void Ensure_CreateReviewHttpPostAction_RedirectsAppropriately()
{
   // Arrange.
   var newReview = CreateMockReview();

   // Act.
   var result = _controller.Create(newReview) as RedirectResult;

   // Assert.
   Assert.IsNotNull(result, "RedirectResult was not returned");
}

Довольно просто. В основном тестируем действие [HttpPost], чтобы обеспечить возврат RedirectResult (шаблон PRG). Я не использую RedirectToRouteResult, потому что ни одна из перегрузок не поддерживает привязные ссылки. Двигаемся дальше.

Теперь я использую Moq, чтобы высмеять Контекст Http, включая переменные сервера, контекст контроллера, сеанс и т.д. Все идет хорошо.

Пока я не ударил эту строку в моем методе действий:

return Redirect(Url.LandingPageWithAnchor(someObject.Uri, review.Uri);

LandingPageWithAnchor - это специальный HTML-помощник:

public static string LandingPageWithAnchor(this UrlHelper helper, string uri1, string uri2)
{
   const string urlFormat = "{0}#{1}";

   return string.Format(urlFormat,
                helper.RouteUrl("Landing_Page", new { uri = uri1}),
                uri2);
}

В принципе, я перенаправляюсь на другую страницу, которая является "целевой страницей" для нового контента, с привязкой к новому обзору. Круто.

Теперь этот метод не выполнялся раньше, потому что UrlHelper был null.

Итак, я сделал это в своем издевательском:

controller.Url = new UrlHelper(fakeRequestContext);

Который получил его дальше, но теперь он не работает, потому что таблицы маршрута не содержат определения для "Landing_Page".

Итак, я знаю, что мне нужно высмеять "что-то", но я не уверен, что это:

a) Таблицы маршрутов

b) Метод UrlHelper.RouteUrl

c) Метод расширения UrlHelper.LandingPageWithAnchor, который я написал

Может ли кто-нибудь дать некоторые рекомендации?

ИЗМЕНИТЬ

Этот конкретный маршрут находится в Области, поэтому я попытался вызвать регистрацию области в моем unit test:

AreaRegistration.RegisterAllAreas();

Но я получаю InvalidOperationException:

Этот метод не может быть вызван во время этапа инициализации перед запуском приложения.

Ответ 1

Получил работу, издеваясь над HttpContext, RequestContext и ControllerContext, регистрируя маршруты, затем создавая UrlHelper с этими маршрутами.

Идет примерно так:

public static void SetFakeControllerContext(this Controller controller, HttpContextBase httpContextBase)
{
    var httpContext = httpContextBase ?? FakeHttpContext().Object;
    var requestContext = new RequestContext(httpContext, new RouteData());
    var controllerContext = new ControllerContext(requestContext, controller);
    MvcApplication.RegisterRoutes();
    controller.ControllerContext = controllerContext;
    controller.Url = new UrlHelper(requestContext, RouteTable.Routes);
}

FakeHttpContext() - помощник Moq, который создает все макеты, серверные переменные, сеанс и т.д.

Ответ 2

Существует конструктор для UrlHelper, который принимает RouteCollection как второй аргумент. Если у вас есть настройка по умолчанию, которую создает MVC для вас, я думаю, что это должно сработать для вас:

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);

controller.Url = new UrlHelper(fakeRequestContext, routes);

Альтернативой является изменение способа запуска вашего приложения, чтобы упростить тестирование и работу. Если вы определяете и связываете вот так:

public interface IMvcApplication
{
    void RegisterRoutes(RouteCollection routes);
    // Other startup operations    
}

С реализацией:

public class MyCustomApplication : IMvcApplication
{
    public void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        // Your route registrations here
    }

    // Other startup operations
}

Затем вы можете изменить свой Global.asax следующим образом:

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        var app = new MyCustomApplication();
        app.RegisterRoutes(RouteTable.Routes);
        // Other startup calls
    }
}

И все еще есть возможность регистрировать маршруты для тестирования. Что-то вроде этого:

private IMvcApplication _app;
private RouteCollection _routes;

[TestInitialize]
public void InitializeTests()
{
    _app = new MyCustomApplication();

    _routes = new RouteCollection();
    _app.RegisterRoutes(_routes);
}

Это также можно использовать для работы с регистрацией области.

private RouteCollection _routes;
private MyCustomAreaRegistration _area;

[TestInitialize]
public void InitTests()
{
    _routes = new RouteCollection();
    var context = new AreaRegistrationContext("MyCustomArea", _routes);

    _area.RegisterArea(context);
}

Ответ 3

Поскольку это тест, ваш тестовый пакет, скорее всего, не читает Global.asax, как вы ожидаете.

Чтобы обойти это, сделайте то, что предложил @ataddeini, и создайте коллекцию маршрутов. В эту коллекцию добавьте новый маршрут, который будет выглядеть как...

var routes = new RouteCollection();
routes.Add(new Route("Landing_Page", new { /*pass route params*/}, null));
var helper = new UrlHelper(fakeRequestContext, routes);