I am working in an ASP.net MVC 5 application. I would like to Unit Test my controller action which looks like this
public ActionResult Search()
{
var vm = SetupSearchViewModel();
return View(vm);
}
All the hard work is done by the SetupSearchViewModel()
method, which itself is an orchestrator calling many different other methods, one of which is this
private string ExtractJsonFile(string filename)
{
var filePath = HttpContext.Server.MapPath(filename);
var json = System.IO.File.ReadAllText(filePath);
return json;
}
I plan on doing many Unit Tests on this particular action, but I'm starting with a very simple Unit Test which checks that the correct type of ActionResult is returned
[Test]
public void Search_Get_ReturnsViewResult()
{
// arrange
var performanceController = PerformanceControllerInstance;
// act
var result = performanceController.Search();
//assert
Assert.IsNotNull(result as ViewResult);
}
The test is failing because of the ExtractJsonFile
method. It uses HttpContext
and that is null
. I am using Rhino Mocks to do the mocking of the various classes.
What would be the best way to Unit Test this? Darin in this thread suggest we avoid HttpContext.Current
if we want our code Unit Tested.
By the way I tried mocking the HttpContext and made it not null, but then the Server
is null
, I can go ahead and mock that too I suppose (I don't know how yet), but is there no better way? I've no problem doing major refactoring if needed.
HttpContext.Server.MapPath
would require an underlying virtual directory provider which would not exist during the unit test. Abstract the path mapping behind a service that you can mock to make the code testable.
public interface IPathProvider {
string MapPath(string path);
}
In the implementation of the concrete service you can make your call to map the path and retrieve the file.
public class ServerPathProvider: IPathProvider {
public string MapPath(string path) {
return HttpContext.Current.Server.MapPath(path);
}
}
you would inject the abstraction into your controller or where needed and used
public MyController : Controller {
public MyController(IPathProvider pathProvider) {
this.pathProvider = pathProvider;
}
//...other code removed for brevity
private string ExtractJsonFile(string filename) {
var filePath = pathProvider.MapPath(filename);
var json = System.IO.File.ReadAllText(filePath);
return json;
}
}
Using your mocking framework of choice you can then mock the provider
[Test]
public void Search_Get_ReturnsViewResult() {
// arrange
IPathProvider mockedPathProvider = //...insert your mock/fake/stub here
var performanceController = PerformanceControllerInstance(mockedPathProvider);
// act
var result = performanceController.Search();
//assert
Assert.IsNotNull(result as ViewResult);
}
and not be coupled to HttpContext
You could even go further and refactor the entire ExtractJsonFile(string filename)
into its own service to get around being tied to disk as well.
public interface IJsonProvider {
string ExtractJsonFile(string filename);
}
This service is now flexible enough to get the file from other sources like web service if needed.