【问题标题】:How to mock application path when unit testing Web App单元测试Web App时如何模拟应用程序路径
【发布时间】:2011-09-19 16:18:25
【问题描述】:

我正在测试 MVC HTML 帮助程序中的代码,该帮助程序在尝试获取应用程序路径时抛出错误:

//appropriate code that uses System.IO.Path to get directory that results in:
string path = "~\\Views\\directory\\subdirectory\\fileName.cshtml";
htmlHelper.Partial(path, model, viewData); //exception thrown here

抛出的异常是

System.Web.HttpException: 应用程序相对虚拟路径 '~/Views/directory/subdirectory/fileName.cshtml' 不能设为绝对路径,因为应用程序的路径未知。

听从How to resolve issue with image path when testing HtmlHelper?的建议
我伪造了(使用最小起订量):

  • Request.Url 返回一个字符串
  • Request.RawUrl 返回一个字符串
  • Request.ApplicationPath 返回一个字符串
  • Request.ServerVariables 返回一个空的 NameValueCollection
  • Response.ApplyAppPathModifier(string virtualPath) 返回一个字符串

还需要什么才能允许此代码在单元测试运行的上下文中运行?
或者
我应该采取什么其他方法来在动态构建的字符串上呈现部分视图?

【问题讨论】:

  • @StuperUser 这不是您问题的完整答案,但如果您将 Mocks 创建为 new Mock<I....>(MockBehavior.Strict),您将获得 ASP.NET MVC 和 Moq 来告诉您您需要什么 - 您将为您尚未实施的设置抛出异常。
  • 谢谢@Ciaran,知道这很有用,但似乎我已经模拟了MockBehavior.Strict 所需的一切,以允许我运行测试以抛出异常。
  • 我通常不对渲染过程进行单元测试。在这个单元测试中,你认为什么是“通过”?
  • 测试是为了构建该路径,但我认为应该将它放在帮助器类中并在那里进行测试。
  • @Hector,如果您希望因某个问题获得良好声誉 (c2.com/cgi/wiki?RubberDucking),请在下面回答,您将获得支持和接受的答案。

标签: c# asp.net asp.net-mvc unit-testing mocking


【解决方案1】:

作为模拟内置 .net 类的替代方法,您可以

public interface IPathProvider
{
    string GetAbsolutePath(string path);
}

public class PathProvider : IPathProvider
{
    private readonly HttpServerUtilityBase _server;

    public PathProvider(HttpServerUtilityBase server)
    {
        _server = server;
    }

    public string GetAbsolutePath(string path)
    {
        return _server.MapPath(path);
    }
}

使用上面的类获取绝对路径。

对于单元测试,您可以模拟和注入 IPathProvider 的实现,该实现将在单元测试环境中工作。

--更新代码

【讨论】:

  • DefaultPathProvider是MVC库中已经实现IPathProvider的类吗?
  • 没有。它是我们自己的。它只是内置 .net 功能的包装。这里的优点是我们不必为单元测试模拟整个请求堆栈。只是 IPathProvider。
【解决方案2】:

不管怎样,我遇到了同样的错误,并通过 System.Web 源跟踪它,发现它发生是因为 HttpRuntime.AppDomainAppVirtualPathObject 为空。

这是 HttpRuntime 单例上的一个不可变属性,初始化如下:

Thread.GetDomain().GetData(key) as String

其中键是".appVPath"。即它来自 AppDomain。可能可以通过以下方式进行欺骗:

Thread.GetDomain().SetData(key, myAbsolutePath)

但老实说,接受的答案中的方法听起来比使用 AppDomain 更好。

【讨论】:

  • 您还需要设置“.appDomain”:AppDomain.CurrentDomain.SetData(".appDomain", "*"); AppDomain.CurrentDomain.SetData(".appVPath", "/appbase");
  • @arni 当我对需要解析虚拟路径的 RazorEngine 渲染进行单元测试时,您的评论和这个答案马上就奏效了!
【解决方案3】:
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
        var moqRequestContext = new Mock<RequestContext>(MockBehavior.Strict);
        request.SetupGet<RequestContext>(r => r.RequestContext).Returns(moqRequestContext.Object);
        var routeData = new RouteData();
        routeData.Values.Add("key1", "value1");
        moqRequestContext.Setup(r => r.RouteData).Returns(routeData);

        request.SetupGet(x => x.ApplicationPath).Returns(PathProvider.GetAbsolutePath(""));

public interface IPathProvider
{
    string GetAbsolutePath(string path);
}

public class PathProvider : IPathProvider
{
     private readonly HttpServerUtilityBase _server;

     public PathProvider(HttpServerUtilityBase server)
     {
        _server = server;
     }

    public string GetAbsolutePath(string path)
    {
        return _server.MapPath(path);
    }
}

【讨论】:

    【解决方案4】:

    Trying to make parts of ASP.NET happy with various types of tests 对我来说似乎很脆弱。而且我倾向于认为,只有在您基本上避免使用 ASP.NET 或 MVC 而是从头开始编写自己的网络服务器时,模拟路由才有效。

    相反,只需使用ApplicationHost.CreateApplicationHost 创建一个正确初始化的AppDomain。然后使用AppDomain.DoCallback 在该域中运行您的测试代码。

    using System;
    using System.Web.Hosting;
    
    public class AppDomainUnveiler : MarshalByRefObject
    {
        public AppDomain GetAppDomain()
        {
            return AppDomain.CurrentDomain;
        }
    }
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var appDomain = ((AppDomainUnveiler)ApplicationHost.CreateApplicationHost(
                typeof(AppDomainUnveiler),
                "/",
                Path.GetFullPath("../Path/To/WebAppRoot"))).GetAppDomain();
            try
            {
                appDomain.DoCallback(TestHarness);
            }
            finally
            {
                AppDomain.Unload(appDomain);
            }
        }
    
        static void TestHarness()
        {
            //…
        }
    }
    

    注意:当我自己尝试此操作时,我的测试运行程序代码位于与 WebAppRoot/bin 目录不同的程序集中。这是一个问题,因为当HostApplication.CreateApplicationHost 创建一个新的AppDomain 时,它会将其基本目录设置为类似于您的WebAppRoot 目录。因此,您必须在可在WebAppRoot/bin 目录中发现的程序集中定义AppDomainUnveiler(因此它必须在您的webapp 的代码库中,并且不能单独存储在测试程序集中,很遗憾)。我建议,如果您希望能够将测试代码保存在单独的程序集中,请在 AppDomainUnveiler 的构造函数中使用 subscribe to AppDomain.AssemblyResolve。一旦您的测试程序集获得AppDomain 对象,它就可以使用AppDomain.SetData 传递有关在何处加载测试程序集的信息。然后您的AssemblyResolve 订阅者可以使用AppDomain.GetData 来发现从哪里加载测试程序集。 (我不确定,但你可以使用SetData/GetData 的对象类型可能非常有限——为了安全起见,我自己只是使用了strings)。这有点烦人,但我认为这是在这种情况下分离关注点的最佳方式。

    【讨论】:

      【解决方案5】:

      我在博客文章中包含了一个解决方案,该解决方案不再可用 (http://blog.jardalu.com/2013/4/23/httprequest_mappath_vs_httpserverutility_mappath)

      完整代码:http://pastebin.com/ar05Ze7p

      Ratna (http://ratnazone.com) 代码使用“HttpServerUtility.MapPath” 将虚拟路径映射到物理文件路径。这个特定的代码有 该产品效果很好。在我们最新的迭代中,我们是 用 HttpRequest.MapPath 替换 HttpServerUtility.MapPath。

      在底层,HttpServerUtility.MapPath 和 HttpRequest.MapPath 是 相同的代码,将产生相同的映射。这两个 当涉及到单元测试时,方法是有问题的。

      在您喜欢的搜索中搜索“server.mappath 空引用” 引擎。您将获得超过 10,000 次点击。几乎所有这些 命中是因为测试代码调用 HttpContext.Current 和 HttpServerUtility.MapPath。当执行 ASP.NET 代码时 HTTP,HttpContext.Current 将为空。

      这个问题(HttpContext.Current 为空)可以很容易地解决 创建一个 HttpWorkerRequest 并初始化 HttpContext.Current 那。这是执行此操作的代码 -

      string appPhysicalDir = @"c:\inetpub\wwwroot";
      string appVirtualDir = "/";
      
      SimpleWorkerRequest request = new SimpleWorkerRequest(appVirtualDir, appPhysicalDir, "/", null, new StringWriter());
      HttpContext.Current = new HttpContext(request);
      

      使用单元测试中的简单代码,HttpContext.Current 是 初始化。事实上,如果你注意到,HttpContext.Current.Server (HttpServerUtility) 也将被初始化。然而,此刻, 代码尝试使用 Server.MapPath,会得到以下异常 扔了。

      System.ArgumentNullException occurred
        HResult=-2147467261
        Message=Value cannot be null.
      Parameter name: path
        Source=mscorlib
        ParamName=path
        StackTrace:
             at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
        InnerException: 
                  HttpContext.Current = context;
      

      其实,如果代码使用HttpContext.Current.Request.MapPath,那就是 会得到同样的例外。如果代码使用 Request.MapPath,则 问题可以在单元测试中轻松解决。下面的代码在 单元测试显示了如何。

      string appPhysicalDir = @"c:\inetpub\wwwroot";
      string appVirtualDir = "/";
      
      SimpleWorkerRequest request = new SimpleWorkerRequest(appVirtualDir, appPhysicalDir, "/", null, new StringWriter());
      FieldInfo fInfo = request.GetType().GetField("_hasRuntimeInfo", BindingFlags.Instance | BindingFlags.NonPublic);
      fInfo.SetValue(request, true);
      HttpContext.Current = new HttpContext(request);
      

      在上面的代码中,request worker 将能够解析地图 小路。但这还不够,因为 HttpRequest 没有 HostingEnvironment 集(解析 MapPath)。很遗憾, 创建 HostingEnvironment 并非易事。所以对于单元测试,一个 创建仅提供 MapPath 功能的“模拟主机”。 同样,这个 MockHost 破解了很多内部代码。这里是 模拟主机的伪代码。完整的代码可以在这里下载: http://pastebin.com/ar05Ze7p

      public MockHost(physicalDirectory, virtualDirectory){ ... }
      public void Setup()
      {
         Create new HostingEnvironment
         Set Call Context , mapping all sub directories as virtual directory
         Initialize HttpRuntime's HostingEnvironment with the created one
      }
      

      使用上面的代码,当 MapPath 被 HttpRequest 调用时,它应该 能够解析路径。

      作为最后一步,在单元测试中,添加以下代码 -

      MockHost host = new MockHost(@"c:\inetpub\wwwroot\", "/");
      host.Setup();
      

      由于现在已经初始化了一个 HostingEnvironment,测试代码 将能够解析虚拟路径 调用 HttpContext.Current.Request.MapPath 方法(连同 HostingEnvironment.MapPath 和 HttpServerUtility.MapPath)。

      在此处下载 MockHost 代码:http://pastebin.com/ar05Ze7p

      【讨论】:

        【解决方案6】:

        一旦您登录应用程序并尝试将任何新 url 添加到 http 上下文并尝试创建 SimpleWorkerRequest,就会发生这种情况。

        在我的情况下,我有一个 url 可以从远程服务器获取文档并将该 url 添加到 http 上下文并尝试验证用户并创建 SimpleWorkerRequest。

        【讨论】:

          猜你喜欢
          • 2021-04-07
          • 2016-11-14
          • 2020-10-05
          • 2016-06-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-09-19
          相关资源
          最近更新 更多