Reputation: 507
I wish to use unit testing in a project that I am involved in. The solution has a UI layer, a Domain Logic Layer and Database base all seperated into physically seperated dlls, so it is pretty standard nowdays.
In the Domain layer we have a class that makes use of
new HttpContextWrapper(HttpContext.Current)
to get the context of the current request to compile a Url for dynamic menus. And as expected everything works fine when running in a web environment as this HttpContext.Current is always set.
However when I come to unit test a controller I hit this line of code and get a Null reference exception.
After some research I have many articles that suggest using Mock to create a Fake Http Context and set this when creating the controller like so:
var httpContext = FakeHttpContext();
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
But this still does not change the HttpContxt.Current and I am still getting a null reference exception.
What am I doing wrong / How can I resolve this exception?
Upvotes: 2
Views: 5254
Reputation: 6678
Cautionary tale
Be wary of using MS Fakes to shim the behavior of HttpContext.Current { get; }
Calls to HttpContext.Current { set; }
will succeed, but the set value will not be usable.
We had someone setting this using [TestInitialize]
in an MSTest based test.
ShimHttpContext.CurrentGet = () => new ShimHttpContext()
{
SessionGet = () => new ShimHttpSessionState()
{
ItemGetString = (name) =>
{
return null;
}
}
};
Upvotes: 0
Reputation: 32768
You should use HttpContextBase
in your domain class and I would recommend to inject that domain class to your controller and so during the unit testing time you can provide an instance of domain class with fake httpcontext to the controller under test.
Ex.
public class DomainClass
{
private readonly HttpContextBase _httpContextBase;
// unit testing
public DomainClass(HttpContextBase httpContextBase)
{
_httpContextBase = httpContextBase;
}
public DomainClass()
{
_httpContextBase = new HttpContextWrapper(HttpContext.Current);
}
}
Upvotes: 1
Reputation: 105
You can use following code to create HttpContext.Current:
[SetUp]
public void Test_SetUp()
{
var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
SetPrivateInstanceFieldValue(result, "m_HostContext", context);
}
Private Method:
private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
{
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var hres = new HttpResponse(sw);
var hreq = new HttpRequest(fileName, url, queryString);
var httpc = new HttpContext(hreq, hres);
return httpc;
}
private static object RunInstanceMethod(object source, string method, object[] objParams)
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var type = source.GetType();
var m = type.GetMethod(method, flags);
if (m == null)
{
throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
}
var objRet = m.Invoke(source, objParams);
return objRet;
}
public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
{
var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
{
throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
}
field.SetValue(source, value);
}
Upvotes: 2
Reputation: 5144
Have you had a look at the MvcContrib TestHelper? I've never used it, but I've seen that others have had success with it. Here is a blog post with some further examples.
Upvotes: 1
Reputation: 13031
In the Domain layer we have a class that makes use of new HttpContextWrapper(HttpContext.Current)
I think you may have a design problem.
Why would the domain layer need knowledge of the GUI content?
You might want to make an argument on your controller constructors to accept IHttpContextWrapper so you can passed in mocked wrappers. Like so:
public MyController()
:this(new HttpContextWrapper())
{
....
}
public MyController(IHttpContextWrapper contextWrapper)
{
....
}
And tests which look like
var context = new Mock<IHttpContextWrapper>();
// setup context
var controller = new MyController(context.Object);
....
Then change your controllers to pass whatever information is required by the domain layer.
Upvotes: 0
Reputation: 3516
You can't use HttpContext.Current in this way. At one of my previous gigs, in order to account for this, we had to create a separate wrapper class and access that property.
Using statics is a very common problem in unit testing.
Per your request for an update:
public static class HttpContextContainer
{
private static HttpContext _ctx;
public static HttpContext Current
{
get
{
if (_ctx == null) _ctx = HttpContext.Current;
return _ctx;
}
set
{
_ctx = value;
}
}
}
Then in your usage you might do something like:
new HttpContextWrapper(HttpContextContainer.Current)
and:
var httpContext = FakeHttpContext();
HttpContextContainer.Current = httpContext;
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
Upvotes: 0