Mock ServerVariables in HttpContext.Current.Request

One of my services uses the server variable provided by IIS with this code

var value = System.Web.HttpContext.Current.Request.ServerVariables["MY_CUSTOM_VAR"]; 

What I tried is to mock this object and insert my own variable / collection and check for several cases (for example, there is no variable, the value is null ...) I can create instances of HttpContext, HttpRequest, HttpResponse and assign them properly, but each one is a simple class with no interface or virtual properties, and ServerVariables initialization takes place 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 to call a private method using 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 to say the assembly is read-only.

Attempt # 2 Replace server variables 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" } }); 

Error with the error that it is impossible to pour NameValueCollection in the HttpServerVarsCollection. This is because the HttpServerVarsCollection is actually an inner class, so I could not either instantiate or apply it.

So the question is, how can I mock ServerVariables or insert a value there? Thanks

+5
source share
3 answers

I found the answer here http://forums.asp.net/t/1125149.aspx

It is not possible to access objects that store server variables. But it can be accessed through reflection. Thus, using reflection, we can set the values โ€‹โ€‹we need. The following extension method will help to 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); } 
+4
source

The problem is that the HttpContext and HttpRequest are not mock-ups.

HttpContext

This is the vintage asp.net context. The problem is that it does not have a base class and is not virtual and therefore unsuitable for testing (cannot mock it). He recommended that you not pass arguments as a function, instead pass variables like HttpContextBase.

HttpContextBase

This is a replacement (new to C # 3.5) for HttpContext. Since it is abstract, it is now a layout. The idea is that your functions are expecting that this context should get one of them. It is specifically implemented by HttpContextWrapper

HttpContextWrapper

Also new in C # 3.5 is a specific implementation of HttpContextBase. To create one on a regular web page, use the new HttpContextWrapper (HttpContext.Current).

The idea is that in order to make your block a module workable, you declare all your variables and function parameters like HttpContextBase and use an IOC scheme like Castle Windsor to get an injection. In normal code, the lock is to enter the equivalent of the 'new HttpContextWrapper (HttpContext.Current)', whereas in the test code you have to get the HttpContextBase layout.

(source: http://www.splinter.com.au/httpcontext-vs-httpcontextbase-vs-httpcontext/ )

You need to wrap it with an HttpContextBase as follows:

 var result = YourMethod(new HttpContextWrapper(HttpContext.Current)); 

Then, when you write a test on YourMethod(HttpContextBase context) , you can easily make fun of HttpContextBase with 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 accessed via context.Object ;

+1
source

This is exactly the case when you need to reorganize your code.

Example refactored code:

 public class MyService { public ServiceResult SomeMethod() { ... var value = GetVariable(System.Web.HttpContext.Current, "some var name"); ... } protected virtual string GetVariable(HttpContext fromContext, string name) { return fromContext.Request.ServerVariables[name]; } } 

In your test:

 class MyTestableService : MyService { protected override string GetVariable(HttpContext fromContext, string name) { return MockedVariables[name]; } public Dictionary<string, string> MockedVariables { get; set; } } [Test] public void TestSomeMethod() { var serviceUnderTest = new MyTestableService { MockedVariables = new Dictionary<string, string> { { "some var name", "var value" } } }; //more arrangements here Assert.AreEqual(expectedResult, serviceUnderTest.SomeMethod()); } 

Of course, you can completely remove this method for a separate dependency.

0
source

Source: https://habr.com/ru/post/1207678/


All Articles