如何创建一个模拟HttpWebRequest和HttpWebResponse对象
本文关键字:HttpWebRequest 模拟 HttpWebResponse 对象 一个 何创建 创建 | 更新日期: 2023-09-27 18:10:27
如何创建模拟HttpWebRequest和HttpWebResponse对象我正在尝试对以下代码进行统一测试
HttpWebrequest request;
if (null != request)
{
var response = (HttpWebResponse)request.GetResponse();
using (var sr = new StreamReader(response.GetResponseStream()))
{
jsonResult = sr.ReadToEnd();
}
var myRecords = SerializationHelper.Deserialize<Records>(jsonResult);
}
您不能为HttpWebResponse创建mock,而不是模拟HttpWebResponse是在接口后面包装调用,并模拟该接口。
这是一个老问题,但也许对某人有用。改编自msdn:中的这个问题
private static WebResponse CreateWebResponse(HttpStatusCode httpStatus, MemoryStream responseObject)
{
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
// Create a listener.
string prefix = "http://localhost:" + port + "/";
HttpListener listener = new HttpListener();
listener.Prefixes.Add(prefix);
listener.Start();
try
{
listener.BeginGetContext((ar) =>
{
HttpListenerContext context = listener.EndGetContext(ar);
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
response.StatusCode = (int)httpStatus;
// Construct a response.
if (responseObject != null)
{
byte[] buffer = responseObject.ToArray();
// Get a response stream and write the response to it.
Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
}
response.Close();
}, null);
WebClient client = new WebClient();
try
{
WebRequest request = WebRequest.Create(prefix);
request.Timeout = 30000;
return request.GetResponse();
}
catch (WebException e)
{
return e.Response;
}
}
finally
{
listener.Stop();
}
return null;
}
在这里,你可以在responseObject中写任何你想要的东西,也可以让它为空。
在我一直在做的一个项目中,我需要在ASMX web服务中进行一些重写,该服务只在生产中抛出错误,以找出该服务实际返回的XML。为了做到这一点,我必须创建一个新的HttpWebResponse对象。基本技巧是使用Activator.CreateInstance(它绕过了构造函数已弃用的事实(。在下面的例子中,我利用了这样一个事实,即我只是克隆了一个现有的HttpWebResponse对象并重置流,但完全从头开始创建一个对象的技术是相同的。
string sLastXML;
public string LastXML
{
get
{
return sLastXML;
}
}
protected override System.Net.WebResponse GetWebResponse(System.Net.WebRequest request)
{
// Get the XML Returned
System.Net.HttpWebResponse oResponse = (System.Net.HttpWebResponse)request.GetResponse();
System.IO.Stream oStream = oResponse.GetResponseStream();
byte[] inStream = new byte[oResponse.ContentLength];
int iActual = 0;
while (iActual < oResponse.ContentLength)
{
iActual += oStream.Read(inStream, iActual, (int)oResponse.ContentLength - iActual);
}
sLastXML = System.Text.Encoding.Default.GetString(inStream);
// Create new stream
System.IO.MemoryStream oNewStream = new System.IO.MemoryStream();
oNewStream.Write(inStream, 0, (int)oResponse.ContentLength);
oNewStream.Position = 0;
// Create new response object
System.Net.HttpWebResponse oNewResponse = (System.Net.HttpWebResponse)System.Activator.CreateInstance(typeof(System.Net.HttpWebResponse));
System.Reflection.PropertyInfo oInfo = oNewResponse.GetType().GetProperty("ResponseStream", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oInfo.SetValue(oNewResponse,oNewStream);
System.Reflection.FieldInfo oFInfo = oNewResponse.GetType().GetField("m_HttpResponseHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_ContentLength", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_Verb", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_StatusCode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_StatusDescription", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_IsMutuallyAuthenticated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_IsVersionHttp11", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_MediaType", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_Uri", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oFInfo = oNewResponse.GetType().GetField("m_UsesProxySemantics", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy);
oFInfo.SetValue(oNewResponse, oFInfo.GetValue(oResponse));
oNewResponse.Cookies = oResponse.Cookies;
return oNewResponse;
}
您可以使用反射来重写需要调整的响应字段,以创建模拟响应。以下是创建状态代码为429:的响应的示例
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
statusCode = (int)response.StatusCode;
ActivateCallback(responseCallback, response, url, string.Empty);
var fieldStatusCode = response.GetType().GetField("m_StatusCode",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
var statusCodeNew = (HttpStatusCode)409;
fieldStatusCode.SetValue(response, statusCodeNew);
string responceBody = "{'"error'":{'"code'":'"AF429'",'"message'":'"Too many requests. Method=GetContents, PublisherId=00000000-0000-0000-0000-000000000000'"}}";
var propStream = response.GetType().GetField("m_ConnectStream",
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
System.IO.MemoryStream ms = new System.IO.MemoryStream(
System.Text.Encoding.UTF8.GetBytes(responceBody));
propStream.SetValue(response, ms);
ms.Position = 0;
我个人没有使用过它,但Moles框架应该让你做你想做的事情。请参阅此处获取一个拦截对DateTime.Now
调用的示例。
模拟HttpResponse可能如下所示,例如:
public SenHttpMockResponse(HttpListenerContext context)
{
HttpListenerResponse response = context.Response;
response.Headers.Add("Content-type", @"application/json");
JObject message = JObject.Parse(@"{'SomeParameterName':'ParameterValue'}");
StreamWriter writer = new StreamWriter(response.OutputStream);
writer.Write(message);
writer.Close();
}
在这种情况下,默认情况下响应代码为200,但您也可以使用Response.StatusCode = 400;
进行调整
要模拟请求,可以使用调试器工具等。例如,Firefox插件HttpRequester非常酷。你可以提出所有类型的请求,你也会看到你的回复。这是一个方便的东西,我会说
您还可以使用SOAP格式化程序来构造一个模拟HttpWebRequest。首先捕获要模拟为字符串的HttpWebRequest,然后可以在单元测试中对其进行自定义,并通过反序列化字符串来重建HttpWebRequest。
这里有一个例子:
static void Main(string[] args)
{
SoapFormatter formatter = new SoapFormatter();
HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create("http://www.google.com");
HttpWebResponse resp = (HttpWebResponse)myReq.GetResponse();
// save this soapRequest as a string and customize it for your mocking up
MemoryStream target = new MemoryStream();
using(target)
{
formatter.Serialize(target, resp);
}
string soapRequest = Encoding.UTF8.GetString(target.GetBuffer());
// now you can use the string to reconstruct the object from the string without needing anything special (other than substituting your own values into the XML)
HttpWebResponse myMockedObject = (HttpWebResponse)formatter.Deserialize(
new MemoryStream(Encoding.UTF8.GetBytes(soapRequest)));
}
我认为包装和抽象是最正确的方法。然而,这是我的破解方案。
只需使用序列化构造函数重载(它也是过时的,但它会生成一个可以抑制的警告(和重写需要更改的方法。
public class FakeHttpWebRequest : HttpWebRequest
{
private static readonly SerializationInfo SerializationInfo = GetSerializationInfo();
private readonly WebResponse response;
public FakeHttpWebRequest(WebResponse response)
#pragma warning disable CS0618 // Type or member is obsolete
: base(
SerializationInfo,
new StreamingContext())
#pragma warning restore CS0618 // Type or member is obsolete
{
this.response = response;
}
public override WebResponse GetResponse()
{
return response;
}
public override Stream GetRequestStream()
{
return new MemoryStream();
}
private static SerializationInfo GetSerializationInfo()
{
// dummy data required for HttpWebRequest() constructor
var serializationInfo = new SerializationInfo(typeof(HttpWebRequest), new FormatterConverter());
serializationInfo.AddValue("_HttpRequestHeaders", new WebHeaderCollection(), typeof(WebHeaderCollection));
serializationInfo.AddValue("_Proxy", null, typeof(IWebProxy));
serializationInfo.AddValue("_KeepAlive", false);
serializationInfo.AddValue("_Pipelined", false);
serializationInfo.AddValue("_AllowAutoRedirect", false);
serializationInfo.AddValue("_AllowWriteStreamBuffering", false);
serializationInfo.AddValue("_HttpWriteMode", 0);
serializationInfo.AddValue("_MaximumAllowedRedirections", 0);
serializationInfo.AddValue("_AutoRedirects", 0);
serializationInfo.AddValue("_Timeout", 0);
serializationInfo.AddValue("_ContentLength", (long)0);
serializationInfo.AddValue("_MediaType", "");
serializationInfo.AddValue("_OriginVerb", "GET");
serializationInfo.AddValue("_ConnectionGroupName", "");
serializationInfo.AddValue("_Version", new Version(1, 0), typeof(Version));
serializationInfo.AddValue("_OriginUri", new Uri("https://fake.uri"), typeof(Uri));
return serializationInfo;
}
}
public class FakeHttpWebResponse : HttpWebResponse
{
private static readonly SerializationInfo SerializationInfo = GetSerializationInfo();
private readonly Stream responseStream;
public FakeHttpWebResponse(HttpStatusCode statusCode, Stream responseStream)
#pragma warning disable CS0618 // Type or member is obsolete
: base(
SerializationInfo,
new StreamingContext())
#pragma warning restore CS0618 // Type or member is obsolete
{
this.StatusCode = statusCode;
this.responseStream = responseStream;
}
public override HttpStatusCode StatusCode { get; }
public override Stream GetResponseStream()
{
return responseStream;
}
private static SerializationInfo GetSerializationInfo()
{
// dummy data required for HttpWebResponse() constructor
var serializationInfo = new SerializationInfo(typeof(HttpWebResponse), new FormatterConverter());
serializationInfo.AddValue("m_HttpResponseHeaders", new WebHeaderCollection(), typeof(WebHeaderCollection));
serializationInfo.AddValue("m_Uri", new Uri("https://fake.uri"), typeof(Uri));
serializationInfo.AddValue("m_Certificate", null, typeof(System.Security.Cryptography.X509Certificates.X509Certificate));
serializationInfo.AddValue("m_Version", new Version(), typeof(Version));
serializationInfo.AddValue("m_StatusCode", (int)HttpStatusCode.HttpVersionNotSupported);
serializationInfo.AddValue("m_ContentLength", (long)0);
serializationInfo.AddValue("m_Verb", "GET");
serializationInfo.AddValue("m_StatusDescription", "");
serializationInfo.AddValue("m_MediaType", "");
return serializationInfo;
}
}
最后,在您的生产代码中,提供了一种将WebRequest创建替换为测试代码的方法。
/// <summary>
/// Delegate to create new web request. Can be set to mock actual requests.
/// </summary>
public Func<string, WebRequest> CreateWebRequest { get; set; } = url => WebRequest.Create(url);
并使用生产代码中的此委托,而不是直接使用WebRequest.Create((。
在测试中,您可以根据需要设置此委托。