Ratna (http://ratnazone.com) code uses "HttpServerUtility.MapPath" for
mapping virtual paths to physical file path. This particular code has
worked very well for the product. In our latest iteration, we are
replacing HttpServerUtility.MapPath with HttpRequest.MapPath.
Under the hoods, HttpServerUtility.MapPath and HttpRequest.MapPath are
the same code and will result in the same mapping. Both of these
methods are problematic when it comes to unit testing.
Search for "server.mappath null reference" in your favourite search
engine. You are going to get over 10,000 hits. Almost all of these
hits are because test code calls HttpContext.Current and
HttpServerUtility.MapPath. When the ASP.NET code is executed without
HTTP, HttpContext.Current will be null.
This issue (HttpContext.Current is null) can be solved very easily by
creating a HttpWorkerRequest and intializing HttpContext.Current with
that. Here is the code to do that -
string appPhysicalDir = @"c:\inetpub\wwwroot";
string appVirtualDir = "/";
SimpleWorkerRequest request = new SimpleWorkerRequest(appVirtualDir, appPhysicalDir, "/", null, new StringWriter());
HttpContext.Current = new HttpContext(request);
With that simple code in unit test, HttpContext.Current is
initialized. Infact, if you notice, HttpContext.Current.Server
(HttpServerUtility) will also be intiailzed. However, the moment, the
code tries to use Server.MapPath, the following 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, it is
going to get the same exception. If the code uses Request.MapPath, the
issue can be resolved in the unit test easily. 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 map
path. This is not enough though, because HttpRequest does not have the
HostingEnvironment set (which resolves MapPath). Unfortunately,
creating a HostingEnvironment is not trivial. So for unit-test, a
"mock host" that just provides the MapPath functionality is created.
Again, this MockHost hacks lot of internal code. Here is the
pseudo-code for the mock host. Complete 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's HostingEnvironment with the created one
}
With the above code when MapPath is called on HttpRequest by it should
be able to resolve the path.
As a last step, in the unit test, add the following code -
MockHost host = new MockHost(@"c:\inetpub\wwwroot\", "/");
host.Setup();
Since now a HostingEnvironment has been initialized, the test code
will be able to resolve virtual paths when
HttpContext.Current.Request.MapPath method is called (along with
HostingEnvironment.MapPath and HttpServerUtility.MapPath).
Download MockHost code here: http://pastebin.com/ar05Ze7p