在HttpContext.Current.Request中模拟ServerVariables

我的一个服务使用IIS通过此类代码提供的服务器变量

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

我试过的是模拟那些对象并插入我自己的变量/集合并检查几个案例(例如变量丢失,值为null …)我能够创建HttpContext,HttpRequest,HttpResponse的实例并分配它们但是它们中的每一个都只是一个没有接口或虚拟属性的普通类,ServerVariables的初始化发生在某个地方。

HttpContext嘲笑:

 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; 

尝试#1通过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"}); 

失败,错误表明集合是只读的。

尝试#2用我自己的实例替换ServerVariables

 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" } }); 

失败,因为无法将NameValueCollection强制转换为HttpServerVarsCollection。 那是因为HttpServerVarsCollection实际上是一个内部类,所以我不能创建它的实例或强制转换它。

所以问题是 – 我如何模拟ServerVariables或在那里插入值? 谢谢

我在http://forums.asp.net/t/1125149.aspx找到了答案

无法访问存储服务器变量的对象。 但它可以使用reflection访问。 因此,使用reflection我们可以设置我们想要的值。 以下扩展方法将帮助设置任何服务器变量。

 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); } 

问题是, HttpContextHttpRequest对象不可模拟。

的HttpContext

这是复古的asp.net上下文。 这个问题是它没有基类而且不是虚拟的,因此不能用于测试(不能模拟它)。 建议不要将它作为函数参数传递,而是传递HttpContextBase类型的变量。

HttpContextBase

这是(新的c#3.5)替换HttpContext。 由于它是抽象的,现在它是可以模仿的。 我们的想法是,您期望传递上下文的函数应该会收到其中一个。 它由HttpContextWrapper具体实现

HttpContextWrapper

在C#3.5中也是新的 – 这是HttpContextBase的具体实现。 要在普通网页中创建其中一个,请使用新的HttpContextWrapper(HttpContext.Current)。

我们的想法是,为了使您的代码单元可测试,您将所有变量和函数参数声明为HttpContextBase类型,并使用IOC框架(例如Castle Windsor)来注入它。 在普通代码中,castle将注入相当于’new HttpContextWrapper(HttpContext.Current)’,而在测试代码中,您将被赋予HttpContextBase的模拟。

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

你需要用HttpContextBase包装它,如下所示:

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

然后当你为YourMethod(HttpContextBase context)编写测试时,你可以使用Moq轻松地模拟HttpContextBase

 var request = new Mock(MockBehavior.Strict); request.Setup(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection{ { "MY_CUSTOM_VAR", "your value here" } }); var context = new Mock(); context.SetupGet(x => x.Request).Returns(request.Object); 

然后可以使用context.Object调用您的HttpContext对象;

这正是您应该重构代码的情况。

重构代码示例:

 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]; } } 

在你的测试中:

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

当然,您可以将方法完全提取到单独的依赖项中。