I consider that Dependency Injection (DI) is a very helpful pattern, I love to use it in order to reduce the coupling in my code and it helps me when writing unit tests. But sometimes the code depends on objects that are difficult or impossible to mock.
The HttpContext class of the ASP .NET MVC framework is one example of this kind of object.
I created the following Controller and View as examples:
public class IndexController : Controller { [HttpGet] public ActionResult Index() { string[] languages = HttpContext.Request.UserLanguages; return View(model:languages); } }
@model string[]
@{
Layout = null;
}
<!--DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@foreach (string language in Model)
{
@language
<br/>
}
</div>
</body>
</html>
This view displays the languages preferences from the request for the visitor:
Now, I want to test my Controller (even if there is no logic in my example) with the following code:
[TestClass] public class IndexControllerTest { [TestMethod] public void IndexActionTest() { IndexController controller = new IndexController(); ViewResult view = controller.Index() as ViewResult; Assert.IsNotNull(view); } }
I just want to check that the view is not null but the test fails. I get a NullReferenceException because the HttpContext property of the Controller is null. This happens because there is no web context when executing the test and it makes sense since the execution occurred in a test context.
The first thing that comes to mind to fix this issue is to set the HttpContext property with a mock. But I cannot do that because this property is read only and thus I am not able to set it when doing the instantiation of the controller in the test method.
And if you need to mock the HttpContext class, you cannot because it is sealed. To avoid this you can use the HttpContextBase abstract class instead but in a test environment you will have to implement it in order to mock it and you don’t want to do this. Why? Because it’s a pain to do, you will have to provide implementation for everything, it’s like creating an entire web context just for the test. And in my case I just need the user languages.
So? What now? Do we give up testing our controllers? Absolutely not, it is possible to achieve our goal without too much complications. From the beginning I tried to mock the wrong thing. What I need is the user languages not the HttpContext, so let’s remove this dependency. I created the following interface with the data my code will use:
public interface IWebContext { string[] UserLanguages { get; } }
And now I will inject this interface in my Controller and I will create a specific implementation that will use the HttpContext to retrieve the languages from the request:
public class RealContext : IWebContext { private HttpContext _context; public RealContext(HttpContext context) { _context = context; } public string[] UserLanguages { get { return _context.Request.UserLanguages; } } } public class IndexController : Controller { IWebContext _context; public IndexController() : this(new RealContext(System.Web.HttpContext.Current)) { } public IndexController(IWebContext context) { _context = context; } [HttpGet] public ActionResult Index() { string[] languages = _context.UserLanguages; return View(model:languages); } }
Note that I created a parameters-less constructor that instantiate the dependency, without this constructor the ASP .NET MVC will not be able to create the Controller (it won’t even compile).
I can now update my test by using a mock for my interface:
public class MockContext : IWebContext { public string[] UserLanguages { get { return new string[1]; } } } [TestClass] public class IndexControllerTest { [TestMethod] public void IndexActionTest() { IndexController controller = new IndexController(new MockContext()); ViewResult view = controller.Index() as ViewResult; Assert.IsNotNull(view); } }
My test is now passing! I removed the dependency between the test and the web context objects, I can now test the logic of my controller without having to set up an awful lot of mocks.
I encapsulated my specific logic inside an abstraction that is far easier to mock. This way I was able to reduced the coupling, I was also able to increase my code coverage and all of it without losing any behavior.
You can use this technique for other properties of the HttpContext like the user agent if you want to apply some logic on it and test it. Or also for static classes/methods (that you can maybe found in your legacy code).
I hope this will help you and as usual do not hesitate to leave a comment.
See you next time!
Reblogged this on Dinesh Ram Kali..
LikeLike
Reblogged this on iReadable.
LikeLike