繁体   English   中英

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

[英]How to mock application path when unit testing Web App

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

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

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

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

作为模拟内置 .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 实现。

--更新代码

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

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

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

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

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

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

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

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

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

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

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

这个问题(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);

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

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

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

 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 并且从头开始编写自己的网络服务器时,模拟路线才有效。

相反,只需使用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目录不同的程序WebAppRoot/bin 这是一个问题,因为当HostApplication.CreateApplicationHost创建一个新的AppDomain ,它会将其基本目录设置为类似于您的WebAppRoot目录的内容。 因此,您必须WebAppRoot/bin目录中可发现的程序WebAppRoot/bin定义AppDomainUnveiler (因此它必须在您的 web 应用程序的代码库中,并且不能单独存储在测试程序集中,不幸的是)。 我建议,如果您希望能够将测试代码保存在单独的程序集中,请在AppDomainUnveiler的构造函数中订阅AppDomain.AssemblyResolve 一旦您的测试程序集获得AppDomain对象,它就可以使用AppDomain.SetData传递有关在哪里加载测试程序集的信息。 然后您的AssemblyResolve订阅者可以使用AppDomain.GetData来发现从哪里加载测试程序集。 (我不确定,但是您可以使用SetData / GetData的对象SetData可能非常有限——为了安全起见,我自己只是使用了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);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM