如何在单元测试Web App时模拟应用程序路径

本文关键字:模拟 应用程序 路径 App Web 单元测试 | 更新日期: 2023-09-27 17:59:57

我正在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.cs.html"设置为绝对路径,因为应用程序的路径未知。

遵循测试HtmlHelper时如何解决图像路径问题的建议
我伪造了(使用Moq):

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

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

如何在单元测试Web App时模拟应用程序路径

作为嘲讽内置.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);
    }
}

使用上面的类可以获得绝对路径。

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

--更新代码

值得一提的是,我遇到了同样的错误,并通过System.Web源找到了它,因为HttpRuntime.AppDomainAppVirtualPathObject为null。

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

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

其中密钥是CCD_ 8。即它来自AppDomain。可能会用来欺骗它

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

但老实说,公认答案中的方法听起来比摆弄AppDomain要好得多。

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

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

拉特纳(http://ratnazone.com)代码使用"HttpServerUtility.MapPath"将虚拟路径映射到物理文件路径。此特定代码具有对产品非常有效。在我们的最新迭代中用HttpRequest.MapPath.替换HttpServerUtility.MapPath

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

在您喜爱的搜索中搜索"server.mappath null reference"发动机你将获得超过10000次点击。几乎所有这些命中是因为测试代码调用HttpContext.Current和HttpServerUtility.MapPath.当在没有HTTP,HttpContext.Current将为null。

这个问题(HttpContext.Current为null)可以通过创建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);

在上面的代码中,请求工作者将能够解析映射路径但是这还不够,因为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

尝试制作ASP的一部分。NET对各种类型的测试很满意,对我来说,这似乎很脆弱。我倾向于相信,只有在基本上避免使用ASP的情况下,嘲讽路线才会奏效。NET或MVC,然后从头开始编写自己的Web服务器。

相反,只需使用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(因此它必须在Web应用程序的代码库中,并且不能单独存储在测试程序集中)。我建议,如果您希望能够将测试代码保存在单独的程序集中,那么可以在AppDomainUnveiler的构造函数中订阅AppDomain.AssemblyResolve。一旦测试程序集获得AppDomain对象,它就可以使用AppDomain.SetData传递有关在哪里加载测试程序集的信息。然后,您的AssemblyResolve订阅者可以使用AppDomain.GetData来发现从何处加载测试程序集。(我不确定,但SetData/GetData可以使用的对象类型可能非常有限——为了安全起见,我自己刚刚使用了string)。这有点烦人,但我认为这是在这种情况下分离关注点的最佳方式。

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

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

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);
    }
}