Unit Testing HostingEnvironment.MapPath()

[4 minute read]

Without getting into all the whys and wtfs, the other day I had a need to dynamically render and inject html snippets into various webpages at runtime. The project I’m working on originally created the html in C# by concatenating string fragments and formatting them with runtime values. With all the requisite escape sequences needed to create valid html (including encapsulated JavaScript), this approach struck me as a bit messy and I decided to take a different route. Instead, I created a cshtml view that could be bound to a model containing all the necessary runtime values. This presented two problems. First, the project’s architecture did not allow this particular view to be rendered in the typical Html.Partial() manner and for a number of reasons restructuring things to facilitate doing so was not an option. Instead, I used RazorEngine to bind the view to a model and render it as a string. This decision solved the first problem, but created another. In order to feed RazorEngine the view, I needed the absolute physical path to it so I could load its text from disk using File.ReadAllText(). I did this by hardcoding the relative path to the view and using HostingEnvironment.MapPath() to return the absolute path at runtime.1 The downside of this choice was reduced testability and increased fragility: If someone moved or renamed the view, or if the hardcoded path was changed, the functionality would break. I wanted to create a unit test that would immediately alert developers should any of these scenarios present themselves, but the reliance on HostingEnvironment made it difficult because during a unit test MapPath() simply returns an empty string since in that context there is no web server running.

Do a search for ‘unit testing HostingEnvironment’ and you’ll find a fair number of similar dilemmas and several good solutions. Two suggestions in particular helped pave my way toward a solution for my specific problem. Merging the ideas presented in the StackOverflow answers found here and here, I created a wrapper around HostingEnvironment that implements an interface which is injected into to the class that renders the html snippet. This allowed me to mock the interface in a way that both verifies the hardcoded virtual path to the view and ensures the view currently exists on disk. If the view has been moved, renamed, or deleted, or if the hardcoded path no longer reflects the file’s actual location, the test will fail. Given that this entire approach to dynamically creating html is a bit unconventional, I felt it was particularly important to highlight the conditions that could cause breakages to the desired functionality.

Here’s what I came up with:

public interface IThingThatRendersHtml
{
	string Render();
}

public interface IApplicationPathResolver
{
	string GetAbsolutePath(string virtualPath);
}

public class ApplicationPathResolver : IApplicationPathResolver
{
	public string GetAbsolutePath(string virtualPath)
	{
		return HostingEnvironment.MapPath(virtualPath);
	}
}

public class ThingThatRendersHtml : IThingThatRendersHtml
{
	private readonly IApplicationPathResolver pathResolver;

	public ThingThatRendersHtml(IApplicationPathResolver pathResolver)
	{
		this.pathResolver = pathResolver;
	}

	public string Render()
	{
		const string virtualPath = "~/Views/Shared/_SomePartialView.cshtml";
		var snippetPath = pathResolver.GetAbsolutePath(virtualPath);

		if (string.IsNullOrEmpty(snippetPath))
			return string.Empty;

		return Razor.Parse(File.ReadAllText(snippetPath), new MyModel
		{
			Id = 69,
			Name = "Dude!"
		});
	}
}

public class MyModel
{
	public string Name { get; set; }
	public int Id { get; set; }
}

And here’s the test class…

[TestFixture]
public class ThingThatRendersHtmlTests
{
	[Test]
	public void ThingThatRendersHtmlHasCorrectViewFilePath()
	{
		var pathResolver = MockRepository.GenerateMock<IApplicationPathResolver>();

		var currentDirectory = Directory.GetCurrentDirectory();
		var endIndex = currentDirectory.IndexOf(@"\MySolution\src\");
		var expectedExternalIntegrationsPath = Path.Combine(currentDirectory.Substring(0, endIndex),
			@"MySolution\src\MVC.Web.Project\Views\Shared");

		const string virtualPath = "~/Views/Shared/_SomePartialView.cshtml";
		var physicalPath = Path.Combine(expectedExternalIntegrationsPath, "_SomePartial.cshtml");
		pathResolver.Stub(x => x.GetAbsolutePath(virtualPath)).Return(physicalPath);

		IThingThatRendersHtml thing = new ThingThatRendersHtml(pathResolver);
		Assert.IsNotNullOrEmpty(thing.Render(), "Failed to locate specified template.");
	}
}

You could argue that I made a simple situation needlessly complicated. The original solution was ugly to look at, but was definitely more easily tested because the dynamic html was all self-contained as a string in the real version of ThingThatRendersHtml. I still think the benefits outweighed the costs. The original code was far less readable, which in my mind automatically means it’s more prone to error. Plus, binding a view to a model makes a far more maintainable solution. Should a need arise to append a new value to the view, or even to remove an obsolete one, it’s easy to do without significant risk of accidentally leaving the html invalid. Concatenating a bunch of string fragments together to create a valid chunk of html and JavaScript is very prone to silly oversights like, for example, forgetting to close a tag. When editing a cshtml file, that kind of error is hard to miss.


Notes

  1. I should point out that this code was not in a Controller, so I did not have access to the Controller.Server.MapPath() method which would perform the same function as HostingEnvironment.MapPath(), but in a more testable fashion.