[英]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
返回一个空的 NameValueCollectionResponse.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.