Mock ServerVariables in the HttpContext.Current.Request
本文关键字:Current Request HttpContext the ServerVariables in Mock | 更新日期: 2023-09-27 18:31:51
我的一个服务通过此类代码使用 IIS 提供的服务器变量
var value = System.Web.HttpContext.Current.Request.ServerVariables["MY_CUSTOM_VAR"];
我尝试的是模拟这些对象并插入我自己的变量/集合并检查一些情况(例如,缺少变量,值为空......我能够创建HttpContext,HttpRequest,HttpResponse的实例并正确分配它们,但是它们中的每一个都只是一个没有接口或虚拟属性的普通类,并且ServerVariables的初始化发生在幕后的某个地方。
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;
尝试 #1 通过反射调用私有方法
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 用我自己的实例替换服务器变量
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 实际上是一个内部类,所以我既不能创建它的实例,也不能强制转换为它。
所以问题是 - 如何模拟服务器变量或在那里插入值?谢谢
我在这里找到了答案 http://forums.asp.net/t/1125149.aspx
无法访问存储服务器变量的对象。但它可以使用反射来访问。因此,使用反射,我们可以设置所需的值。以下扩展方法将有助于设置任何服务器变量。
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);
}
问题是,HttpContext
和HttpRequest
对象是不可模拟的。
HttpContext
这是复古 asp.net 背景。这样做的问题是它 没有基类,也不是虚拟的,因此不能用于测试 (不能嘲笑它)。建议不要将其作为函数传递 参数,而是传递 HttpContextBase 类型的变量。
HttpContextBase
这是 HttpContext 的(c# 3.5 的新增功能)替代品。既然是 抽象的,它现在是可嘲笑的。这个想法是你的函数 期望传递上下文应期望接收其中之一。 它由HttpContextWrapper具体实现
HttpContextWrapper
也是 C# 3.5 中的新功能 - 这是 HttpContextBase.要在普通网页中创建其中一个,请使用 HttpContextWrapper(HttpContext.Current).
这个想法是,为了使你的代码单元可测试,你声明所有 变量和函数参数的类型为 HttpContextBase,以及 使用IOC框架,例如温莎城堡来注入它。在正常情况下 代码,城堡是注入相当于"新 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<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);
然后可以使用 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<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());
}
当然,您可以将该方法完全提取到单独的依赖项中。