How to wave the application path while unit testing a web application

I am testing the code in the MVC HTML helper, which throws an error when trying to get the path to the application:

//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 

The exception is

System.Web.HttpException: relative application virtual path ~ ~ / Views / directory / subdirectory / fileName.cshtml cannot be absolute because the application path is unknown.

Following the tips How to solve the problem using the image path when testing HtmlHelper? I faked (using Moq):

  • Request.Url to return a string
  • Request.RawUrl to return a string
  • Request.ApplicationPath to return a string
  • Request.ServerVariables to return null NameValueCollection
  • Response.ApplyAppPathModifier(string virtualPath) to return a string

What else is needed to allow the launch of this code in the context of running unit test?
Or
What other approach should be taken to partially represent a dynamically constructed string?

+7
source share
5 answers

As an alternative to the bullying built-in .net classes, you can

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

Use the above class to get absolute paths.

And for unit testing, you can ridicule and implement an IPathProvider implementation that will work in a unit testing environment.

- UPDATED CODE

+9
source

For what it's worth, I ran into the same error and followed it through the System.Web source to find it, because HttpRuntime.AppDomainAppVirtualPathObject is null.

This is an immutable property of the HttpRuntime singleton, initialized as follows:

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

where is the key ".appVPath" . that is, it comes from AppDomain. Perhaps you can fool him:

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

But honestly, the approach in the accepted answer sounds much better than spoofing AppDomain.

+3
source

Trying to make parts of ASP.NET happy with different types of tests seems rather fragile to me. And I tend to believe that the mocking route only works if you basically avoid using ASP.NET or MVC and instead write your own web server from scratch.

Instead, just use ApplicationHost.CreateApplicationHost to create a properly initialized AppDomain . Then run your test code from this domain using 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() { //… } } 

Note. When I tried this, the code of my test run itself was in a separate assembly from the WebAppRoot/bin . This is a problem because when HostApplication.CreateApplicationHost creates a new AppDomain , it sets its base directory somehow like your WebAppRoot directory. Therefore, you must define the AppDomainUnveiler in the assembly, which can be found in the WebAppRoot/bin (therefore, it must be in your webapps database and cannot be stored separately in the test assembly, unfortunately). I suggest that if you want to keep your test code in a separate assembly, subscribe to AppDomain.AssemblyResolve in the AppDomainUnveiler constructor. After your test assembly receives the AppDomain object, it can use AppDomain.SetData to pass information about where to load the test assembly. Then your AssemblyResolve subscriber can use AppDomain.GetData to find out where to download the test assembly. (I'm not sure, but the types of objects that you can SetData / GetData can be quite limited - I just used string to be safe). This is a little annoying, but I think this is the best way to share the problems in this situation.

+1
source

I am including a solution from a blog post that is no longer available ( http://blog.jardalu.com/2013/4/23/httprequest_mappath_vs_httpserverutility_mappath )

Full code: http://pastebin.com/ar05Ze7p

Ratna ( http://ratnazone.com ) uses the "HttpServerUtility.MapPath" code to map virtual paths to the physical file path. This particular code has worked perfectly for the product. In our last iteration, we replaced HttpServerUtility.MapPath with HttpRequest.MapPath.

Under the hoods, HttpServerUtility.MapPath and HttpRequest.MapPath the same code and will lead to the same display. Both of these methods are problematic when it comes to unit testing.

Find "server.mappath null reference" in your favorite search engine. You will receive more than 10,000 hits. Almost all of these hits are because the test code calls HttpContext.Current and HttpServerUtility.MapPath. When ASP.NET code runs without HTTP, HttpContext.Current will be null.

This problem (HttpContext.Current is null) can be solved very easily by creating an HttpWorkerRequest and initializing HttpContext.Current with that. Here is the code for this -

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

With this simple code in unit test, HttpContext.Current is initialized. Infact, if you notice, HttpContext.Current.Server (HttpServerUtility) will also be indexed. However, at that moment, the code is trying to use Server.MapPath, the next exception will get thrown.

 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; 

Infact, if the code uses HttpContext.Current.Request.MapPath, this is getting the same exception. If Request.MapPath is used in the code, the problem can be easily resolved in the unit test. The following code in unit test shows how.

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

In the above code, the request worker will be able to resolve the track card. This is not enough because HttpRequest does not have a HostingEnvironment Set (which allows MapPath). Unfortunately, creating a HostingEnvironment is not trivial. Thus, for the unit test "mock host", which only provides MapPath functionality, is created. Again, this MockHost handles a lot of internal code. Here's the pseudo code for the layout node. The full code can be downloaded here: 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 HostingEnvironment with the created one } 

With the above code, when MapPath is called on HttpRequest, it should be able to resolve the path.

As a last step in unit test add the following code -

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

Since HostingEnvironment is now initialized, test paths can resolve virtual paths when the HttpContext.Current.Request.MapPath method is called (along with HostingEnvironment.MapPath and HttpServerUtility.MapPath).

Download the MockHost code here: http://pastebin.com/ar05Ze7p

+1
source
 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); } } 
-one
source

All Articles