MVC3 HttpContext单元测试/模拟选项

时间:2011-10-31 16:56:29

标签: asp.net-mvc unit-testing mocking httpcontext

所以这真的适用于几个不同的类,如HttpContext,ConfigurationManager等。有几种不同的方法来处理这个,我总是用包装类来处理这些东西,但我想看看最常见的社区实践是...

  1. 包装类 - 例如我会有一个HttpContextService,它通过构造函数传递,通过平面方法调用公开所有相同的功能。
  2. 包装类(第2部分) - 例如我将拥有SPECIFIC服务类,如MembershipService,它在幕后杠杆化HttpContext。函数与1相同,但命名方案/使用模式稍有不同,因为您通过特定服务而不是单个包装器公开特定函数。缺点是需要注入的服务类数量增加,但是当你不需要整体包装器的所有功能时,你可以获得一些模块化。
  3. ActionFilters和参数 - 使用ActionFilter自动传递基于每个函数所需的某些值。仅限MVC,并限制您使用控制器方法,而1和2可以在整个项目中使用,甚至可以与此选项一起使用。
  4. 直接模拟HttpContextBase并设置ControllerContext - 有几种模拟框架扩展方法可以帮助解决这个问题,但实际上需要您根据需要直接设置。不需要抽象,这很好,并且可以在非控制器测试中重复使用。仍然留下了关于ConfigurationManager和其他静态方法调用的问题,所以你可能最终注入了ANYWAY,但是让HttpContext以其他方式被访问。
  5. 现在我有点做第一,所以我有一个HttpContextService和一个ConfigurationManagerService等,然后我注入了,虽然我将来倾向于2。 3对于我的口味来说似乎有点过于混乱,但我可以看到控制器方法的吸引力,并且对于使用这些静态类的其他代码区域需要一个完全独立的解决方案,这对我来说是一种穷人。 .. 4对我来说仍然很有趣,因为它在基本功能方面似乎是最“自然”的,并且利用了MVC的内置方法。

    那么这里流行的最佳实践是什么?人们在野外看到和使用的是什么?

1 个答案:

答案 0 :(得分:0)

已经为HttpContext,HttpRequest,HttpResponse等提供了“包装”类.MVC框架使用这些类,您可以通过控制器上下文向Controller提供它们的模拟。您不需要模拟控制器上下文,因为您可以使用适当的值创建一个。我发现很难模仿的唯一的东西是助手,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;
    }
}