I am testing code in a MVC HTML helper that throws an error when trying to get the application path:
//appropriate code that uses System.IO.Path to get direc
Trying to make parts of ASP.NET happy with various types of tests seems, to me, to be quite fragile. And I am inclined to believe that the mocking route only works if you basically avoid using ASP.NET or MVC and, instead, write your own webserver from scratch.
Instead, just use ApplicationHost.CreateApplicationHost to create a properly-initialized AppDomain
. Then run your test code from within that 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 trying this myself, my test runner code was in a separate assembly from the WebAppRoot/bin
directory. This is an issue because, when HostApplication.CreateApplicationHost
creates a new AppDomain
, it sets its base directory to something like your WebAppRoot
directory. Therefore, you must define AppDomainUnveiler
in an assembly that is discoverable in the WebAppRoot/bin
directory (so it must be in your webapp’s codebase and cannot be stored separately in a testing assembly, unfortunately). I suggest that if you want to be able to keep your test code in a separate assembly, you subscribe to AppDomain.AssemblyResolve in AppDomainUnveiler
’s constructor. Once your testing assembly gets the AppDomain
object, it can use AppDomain.SetData to pass along information about where to load the testing assembly. Then your AssemblyResolve
subscriber can use AppDomain.GetData to discover where to load the test assembly from. (I’m not sure, but the sort of objects you can SetData
/GetData
might be quite limited—I’ve just used string
s myself to be safe). This is a bit annoying, but I think it is the best way to separate concerns in this situation.