unit testingWeb 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 

引发的exception是

System.Web.HttpException:无法使应用程序相对虚拟路径’〜/ Views / directory / subdirectory / fileName.cshtml’成为绝对路径,因为应用程序的路径未知。

遵循如何在测试HtmlHelper时解决图像路径问题的建议?
我假装(使用Moq):

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

还需要什么才能允许此代码在unit testing运行的上下文中运行?
要么
我应该采取什么其他方法来渲染动态构建的字符串的部分视图?

作为模拟内置.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 unit测试,您可以模拟并注入可在unit testing环境中工作的IPathProvider实现。

– 更新代码

为了它的价值,我遇到了同样的错误,然后通过System.Web源查找它,因为HttpRuntime.AppDomainAppVirtualPathObject为null。

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

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

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

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

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

在我看来, 试图使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目录位于一个单独的程序WebAppRoot/bin 。 这是一个问题,因为当HostApplication.CreateApplicationHost创建新的AppDomain ,它会将其基本目录设置为类似于WebAppRoot目录的目录。 因此,您必须WebAppRoot/bin目录中可发现的程序WebAppRoot/bin定义AppDomainUnveiler (因此它必须位于您的webapp的代码库中,并且不能单独存储在测试程序集中)。 我建议如果您希望能够将测试代码保存在单独的程序集中,则可以在AppDomainUnveiler的构造函数中订阅AppDomain.AssemblyResolve 。 一旦您的测试程序集获得AppDomain对象,它就可以使用AppDomain.SetData传递有关加载测试程序集的位置的信息。 然后, AssemblyResolve订阅者可以使用AppDomain.GetData来发现从哪里加载测试程序集。 (我不确定,但是你可以使用SetData / GetData SetData GetData可能非常有限 – 我自己使用了string来保证安全)。 这有点烦人,但我认为这是在这种情况下分离问题的最佳方式。

我在博客文章中提供了一个解决方案,该文章已不再可用( 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是相同的代码,将导致相同的映射。 当涉及unit testing时,这两种方法都存在问题。

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

通过创建HttpWorkerRequest并使用初始化HttpContext.Current,可以非常轻松地解决此问题(HttpContext.Current为null)。 这是执行此操作的代码 –

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

使用unit testing中的简单代码,初始化HttpContext.Current。 事实上,如果你注意到,HttpContext.Current.Server(HttpServerUtility)也将被启用。 但是,当下,代码尝试使用Server.MapPath,以下exception将被抛出。

 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,它将获得相同的exception。 如果代码使用Request.MapPath,则可以在unit testing中轻松解决问题。 unit testing中的以下代码显示了如何。

 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并非易事。 因此,对于unit testing,创建了一个仅提供MapPathfunction的“模拟主机”。 同样,这个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时应该能够解析路径。

最后一步,在unit testing中,添加以下代码 –

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

从现在开始初始化HostingEnvironment,测试代码将能够在调用HttpContext.Current.Request.MapPath方法时解析虚拟路径(以及HostingEnvironment.MapPath和HttpServerUtility.MapPath)。

在这里下载MockHost代码: http ://pastebin.com/ar05Ze7p

 var request = new Mock(MockBehavior.Strict); var moqRequestContext = new Mock(MockBehavior.Strict); request.SetupGet(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); } }