Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock ServerVariables in the HttpContext.Current.Request

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

like image 345
Andriy Horen Avatar asked Nov 25 '14 16:11

Andriy Horen


People also ask

What is HttpContext current request ServerVariables?

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.

What is ServerVariables in asp net?

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.


2 Answers

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);
    }
like image 60
Birey Avatar answered Oct 05 '22 07:10

Birey


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;

like image 45
Vee Trairattanapa Avatar answered Oct 05 '22 05:10

Vee Trairattanapa