MVC3 HttpContext юнит-тестирование / макет опций - PullRequest
2 голосов
/ 31 октября 2011

Так что это действительно относится к нескольким различным классам, таким как HttpContext, ConfigurationManager и т. Д. Есть несколько различных способов справиться с этим, и я всегда использовал классы-обертки для обработки этого материала, но я хотел посмотреть, что является наиболее распространенной практикой сообществаis ...

  1. Классы Wrapper - например, у меня будет HttpContextService, который я передаю через конструктор, который предоставляет все те же функциональные возможности через вызовы плоских методов.
  2. Классы Wrapper (часть 2) - например, у меня были бы КОНКРЕТНЫЕ классы обслуживания, такие как MembershipService, который НАПРАВЛЯЕТ HttpContext за кулисами.Функции такие же, как 1, но схема именования / схема использования немного отличается, так как вы предоставляете определенные функции через определенные службы вместо одной монолитной оболочки.Недостатком является то, что количество классов обслуживания, которые нужно внедрить, увеличивается, но вы получаете некоторую модульность, когда вам не нужны все функции монолитной оболочки.
  3. ActionFilters и параметры - используйте ActionFilterавтоматически передавать определенные значения, необходимые для каждой функции.Только MVC, и ограничивает вас методами контроллера, в то время как 1 и 2 могут использоваться в общем виде по всему проекту или даже в сочетании с этой опцией.
  4. Непосредственное макетирование HttpContextBase и настройка ControllerContext - существует несколько расширений фреймворкаметоды, чтобы помочь с этим, но, по сути, требует от вас, чтобы установить вещи по мере необходимости.Не требует абстракций, что приятно, и может быть повторно использовано в неконтроллерных тестах.Тем не менее, остается открытым вопрос для ConfigurationManager и других вызовов статических методов, так что вы можете в конечном итоге внедрить этот ЛЮБОЙ путь, но оставив HttpContext для доступа другим способом.

Прямо сейчас я вродеделаю номер 1, поэтому у меня есть HttpContextService, ConfigurationManagerService и т. д., которые я затем внедряю, хотя в будущем я склоняюсь к 2.Кажется, что 3 немного запутанно для моих вкусов, но я вижу привлекательность методов контроллера, и необходимость совершенно отдельного решения для других областей кода, которые также используют эти статические классы, делает этот вид бедным для меня... 4 по-прежнему интересен для меня, поскольку кажется наиболее «естественным» с точки зрения базовой функциональности и использует встроенные методологии MVC.

Итак, какова здесь преобладающая наилучшая практика?Что люди видят и используют в дикой природе?

1 Ответ

0 голосов
/ 31 октября 2011

Уже существуют классы-оболочки для HttpContext, HttpRequest, HttpResponse и т. Д. Среда MVC использует их, и вы можете передавать их макеты в Контроллер через контекст контроллера. Вам не нужно имитировать контекст контроллера, так как вы можете просто создать его с соответствующими значениями. Единственное, что мне было трудно высмеять - это помощники, UrlHelper и HtmlHelper. Те имеют некоторые относительно глубокие зависимости. Вы можете подделать их несколько разумным способом, как показано ниже. UrlHelper.

 var httpContext = MockRepository.GenerateMock<HttpContextBase>();
 var routeData = new RoutedData();

 var controller = new HomeController();
 controller.ControllerContext = new ControllerContext( httpContext, routeData, controller );
 controller.Url = UrlHelperFactory.CreateUrlHelper( httpContext, routeDate );

, где

 public static class UrlHelperFactory
 {
    public static UrlHelper CreateUrlHelper( HttpContextBase httpContext, RouteData routeData )
    {
        return CreateUrlHelper( httpContext, routeData, "/" );
    }

    public static UrlHelper CreateUrlHelper( HttpContextBase httpContext, RouteData routeData, string url )
    {
        string urlString = string.Format( "http://localhost/{0}/{1}/{2}", routeData.Values["controller"], routeData.Values["action"], routeData.Values["id"] ).TrimEnd( '/' );

        var uri = new Uri( urlString );

        if (httpContext.Request == null)
        {
            httpContext.Stub( c => c.Request ).Return( MockRepository.GenerateStub<HttpRequestBase>() ).Repeat.Any();
        }

        httpContext.Request.Stub( r => r.Url ).Return( uri ).Repeat.Any();
        httpContext.Request.Stub( r => r.ApplicationPath ).Return( "/" ).Repeat.Any();

        if (httpContext.Response == null)
        {
            httpContext.Stub( c => c.Response ).Return( MockRepository.GenerateStub<HttpResponseBase>() ).Repeat.Any();
        }
        if (url != "/")
        {
            url = url.TrimEnd( '/' );
        }

        httpContext.Response.Stub( r => r.ApplyAppPathModifier( Arg<string>.Is.Anything ) ).Return( url ).Repeat.Any();

        return new UrlHelper( CreateRequestContext( httpContext, routeData ), GetRoutes() );
    }

    public static RequestContext CreateRequestContext( HttpContextBase httpContext, RouteData routeData )
    {
        return new RequestContext( httpContext, routeData );
    }

    // repeat your route definitions here!!!
    public static RouteCollection GetRoutes()
    {
        RouteCollection routes = new RouteCollection();
        routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );


        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "home", action = "index", id = "" }  // Parameter defaults
        );

        return routes;
    }
}
...