One of my services uses server variable provided by IIS through such code
var value = System.Web.HttpContext.Current.Request.ServerVariables["MY_CUSTOM_VAR"];
What I've tried is to mock those object and insert my own variable/collection and check few cases(e.g. variable is missing, value is null ...) I'm able to create instances of HttpContext, HttpRequest, HttpResponse and assign them properly however each of them is just a plain class without interface or virtual properties and initialization of ServerVariables happens somewhere under the hood.
HttpContext mocking:
var httpRequest = new HttpRequest("", "http://excaple.com/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContextMock = new HttpContext(httpRequest, httpResponse);
HttpContext.Current = httpContextMock;
Attempt #1 Call private method via reflection
var serverVariables = HttpContext.Current.Request.ServerVariables;
var serverVariablesType = serverVariables.GetType();
MethodInfo addStaticMethod = serverVariablesType.GetMethod("AddStatic", BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
addStaticMethod.Invoke(serverVariables, new object[] {"MY_CUSTOM_VAR", "value"});
Failed with error saying that collection is readonly.
Attempt #2 Replace ServerVariables with my own instance
var request = HttpContext.Current.Request;
var requestType = request.GetType();
var variables = requestType.GetField("_serverVariables", BindingFlags.Instance | BindingFlags.NonPublic);
variables.SetValue(request, new NameValueCollection
{
{ "MY_CUSTOM_VAR", "value" }
});
Failed with error that it's not possible to cast NameValueCollection to HttpServerVarsCollection. That's because HttpServerVarsCollection is actually a internal class so I couldn't either to make an instance of it or cast to it.
So the question is - How can I mock ServerVariables or insert value there? Thanks
HttpContext.Current.Request.ServerVariables("LOGON_USER") Request.ServerVariables("LOGON_USER") it will work only when Windows Integrated Authentication is turned on and Anonymous. Access is turned off. in this case, the Request.ServerVariables("LOGON_USER") will return the network.
The ServerVariables collection retrieves the values of predetermined environment variables. Syntax Request.ServerVariables (server environment variable) Parameters server environment variable. Specifies the name of the server environment variable to retrieve.
I found the answer here http://forums.asp.net/t/1125149.aspx
It is not possible to access the objects that stores server variables. But it can be accessed using reflection. So using reflection we can set the values that we want. Following extension method will help set any server variable.
public static void AddServerVariable(this HttpRequest request, string key, string value)
{
BindingFlags temp = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
MethodInfo addStatic = null;
MethodInfo makeReadOnly = null;
MethodInfo makeReadWrite = null;
Type type = request.ServerVariables.GetType();
MethodInfo[] methods = type.GetMethods(temp);
foreach (MethodInfo method in methods)
{
switch (method.Name)
{
case "MakeReadWrite":
makeReadWrite = method;
break;
case "MakeReadOnly":
makeReadOnly = method;
break;
case "AddStatic":
addStatic = method;
break;
}
}
makeReadWrite.Invoke(request.ServerVariables, null);
string[] values = { key, value };
addStatic.Invoke(request.ServerVariables, values);
makeReadOnly.Invoke(request.ServerVariables, null);
}
The problem is, HttpContext
and HttpRequest
objects are not mockable.
HttpContext
This is the vintage asp.net context. The problem with this is that it has no base class and isn't virtual, and hence is unusable for testing (cannot mock it). It's recommended to not pass it around as function arguments, instead pass around variables of type HttpContextBase.
HttpContextBase
This is the (new to c# 3.5) replacement to HttpContext. Since it is abstract, it is now mockable. The idea is that your functions that expect to be passed a context should expect to receive one of these. It is concretely implemented by HttpContextWrapper
HttpContextWrapper
Also new in C# 3.5 - this is the concrete implementation of HttpContextBase. To create one of these in a normal webpage, use new HttpContextWrapper(HttpContext.Current).
The idea is that to make your code unit-testable, you declare all your variables and function parameters to be of type HttpContextBase, and use an IOC framework eg Castle Windsor to get it injected. In normal code, castle is to inject the equivalent of 'new HttpContextWrapper(HttpContext.Current)', whereas in test code you're to be given a mock of HttpContextBase.
(source: http://www.splinter.com.au/httpcontext-vs-httpcontextbase-vs-httpcontext/)
You need to wrap it with HttpContextBase
like so:
var result = YourMethod(new HttpContextWrapper(HttpContext.Current));
Then when you write your test for YourMethod(HttpContextBase context)
, you can easily mock the HttpContextBase
using Moq:
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
request.Setup(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection{
{ "MY_CUSTOM_VAR", "your value here" }
});
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
Then your HttpContext object can be called with context.Object
;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With