我终于开始创建一些可以与RESTful Web界面一起使用的应用程序了,但是,我担心每次我按F5来运行一系列测试时,我都会锤打他们的服务器。
基本上,我需要获得一系列Web响应,以便可以测试是否正确解析了各种响应,而不是每次都访问它们的服务器,我认为我可以这样做一次,保存XML,然后在本地工作。
但是,我看不到如何"模拟" WebResponse,因为(AFAIK)它们只能由WebRequest.GetResponse实例化。
你们如何嘲笑这种事情?你呢?我只是真的不喜欢我要锤打他们的服务器:S我不想过多更改代码,但是我希望有一种优雅的方法。
接受后更新
威尔的答案是我需要拍打脸,我知道我错过了一个基本要点!
-
创建一个接口,该接口将返回代表XML的代理对象。
-
两次实现该接口,一种使用WebRequest,另一种返回静态"响应"。
-
然后,接口实现要么根据响应实例化返回类型,要么静态XML。
-
然后,您可以在测试或生产时将所需的类传递给服务层。
敲完代码后,我将粘贴一些示例。
我在寻求做完全相同的事情时发现了这个问题。在任何地方都找不到答案,但经过进一步挖掘后发现,.Net Framework已内置对此功能的支持。
您可以使用WebRequest.RegisterPrefix注册工厂对象,当使用该前缀(或URL)时,WebRequest.Create将调用该对象。工厂对象必须实现具有单个方法Create的IWebRequestCreate,该方法返回WebRequest。在这里,您可以返回模拟WebRequest。
我在上放了一些示例代码
http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/
这是不需要模拟的解决方案。您实现WebRequest的所有三个组件:IWebRequestCreate WebRequest和WebResponse。见下文。我的示例生成失败的请求(通过抛出WebException),但应该能够使其适应发送"真实"响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| class WebRequestFailedCreate : IWebRequestCreate {
HttpStatusCode status;
String statusDescription;
public WebRequestFailedCreate(HttpStatusCode hsc, String sd) {
status = hsc;
statusDescription = sd;
}
#region IWebRequestCreate Members
public WebRequest Create(Uri uri) {
return new WebRequestFailed(uri, status, statusDescription);
}
#endregion
}
class WebRequestFailed : WebRequest {
HttpStatusCode status;
String statusDescription;
Uri itemUri;
public WebRequestFailed(Uri uri, HttpStatusCode status, String statusDescription) {
this.itemUri = uri;
this.status = status;
this.statusDescription = statusDescription;
}
WebException GetException() {
SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new System.Runtime.Serialization.FormatterConverter());
StreamingContext sc = new StreamingContext();
WebHeaderCollection headers = new WebHeaderCollection();
si.AddValue("m_HttpResponseHeaders", headers);
si.AddValue("m_Uri", itemUri);
si.AddValue("m_Certificate", null);
si.AddValue("m_Version", HttpVersion.Version11);
si.AddValue("m_StatusCode", status);
si.AddValue("m_ContentLength", 0);
si.AddValue("m_Verb","GET");
si.AddValue("m_StatusDescription", statusDescription);
si.AddValue("m_MediaType", null);
WebResponseFailed wr = new WebResponseFailed(si, sc);
Exception inner = new Exception(statusDescription);
return new WebException("This request failed", inner, WebExceptionStatus.ProtocolError, wr);
}
public override WebResponse GetResponse() {
throw GetException();
}
public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) {
Task<WebResponse> f = Task<WebResponse>.Factory.StartNew (
_ =>
{
throw GetException();
},
state
);
if (callback != null) f.ContinueWith((res) => callback(f));
return f;
}
public override WebResponse EndGetResponse(IAsyncResult asyncResult) {
return ((Task<WebResponse>)asyncResult).Result;
}
}
class WebResponseFailed : HttpWebResponse {
public WebResponseFailed(SerializationInfo serializationInfo, StreamingContext streamingContext)
: base(serializationInfo, streamingContext) {
}
} |
您必须创建HttpWebResponse子类,因为否则无法创建子类。
棘手的部分(在GetException()方法中)提供了无法覆盖的值,例如StatusCode,这就是我们最好的伙伴SerializaionInfo的来历!您可以在此处提供无法覆盖的值。显然,覆盖您能够使用的部分(HttpWebResponse的部分),以获取其余的信息。
如何在所有这些AddValue()调用中获得"名称"?来自异常消息!可以轮流告诉我每个人,直到我高兴为止。
现在,编译器将抱怨"过时",但这仍然有效,包括.NET Framework版本4。
这是一个(通过)测试用例,以供参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| [TestMethod, ExpectedException(typeof(WebException))]
public void WebRequestFailedThrowsWebException() {
string TestURIProtocol = TestContext.TestName;
var ResourcesBaseURL = TestURIProtocol +"://resources/";
var ContainerBaseURL = ResourcesBaseURL +"container" +"/";
WebRequest.RegisterPrefix(TestURIProtocol, new WebRequestFailedCreate(HttpStatusCode.InternalServerError,"This request failed on purpose."));
WebRequest wr = WebRequest.Create(ContainerBaseURL);
try {
WebResponse wrsp = wr.GetResponse();
using (wrsp) {
Assert.Fail("WebRequest.GetResponse() Should not have succeeded.");
}
}
catch (WebException we) {
Assert.IsInstanceOfType(we.Response, typeof(HttpWebResponse));
Assert.AreEqual(HttpStatusCode.InternalServerError, (we.Response as HttpWebResponse).StatusCode,"Status Code failed");
throw we;
}
} |
我之前发现了以下博客,该博客解释了使用Microsoft Moles的一种很好的方法。
Mocking HttpWebResponse with Moles
简而言之,该解决方案建议以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| [TestMethod]
[HostType("Moles")]
[Description("Tests that the default scraper returns the correct result")]
public void Scrape_KnownUrl_ReturnsExpectedValue()
{
var mockedWebResponse = new MHttpWebResponse();
MHttpWebRequest.AllInstances.GetResponse = (x) =>
{
return mockedWebResponse;
};
mockedWebResponse.StatusCodeGet = () => { return HttpStatusCode.OK; };
mockedWebResponse.ResponseUriGet = () => { return new Uri("http://www.google.co.uk/someRedirect.aspx"); };
mockedWebResponse.ContentTypeGet = () => { return"testHttpResponse"; };
var mockedResponse ="<html>
" +
" <head></head>
" +
" <body>
" +
" Hello World
" +
" </body>
" +
"</html>";
var s = new MemoryStream();
var sw = new StreamWriter(s);
sw.Write(mockedResponse);
sw.Flush();
s.Seek(0, SeekOrigin.Begin);
mockedWebResponse.GetResponseStream = () => s;
var scraper = new DefaultScraper();
var retVal = scraper.Scrape("http://www.google.co.uk");
Assert.AreEqual(mockedResponse, retVal.Content,"Should have returned the test html response");
Assert.AreEqual("http://www.google.co.uk/someRedirect.aspx", retVal.FinalUrl,"The finalUrl does not correctly represent the redirection that took place.");
} |
你不能最好的办法是将其包装在代理对象中,然后对其进行模拟。另外,您必须使用一个模拟框架,该框架可以拦截无法模拟的类型,例如TypeMock。但是,您在谈论的是雄鹿。最好做一点包裹。
显然,您可以做一些额外的工作。在此处查看投票最高的答案。
这不是一个完美的解决方案,但它以前对我有用,并且为简单起见值得特别注意:
HTTP模拟器
也是typemock论坛中记录的typemock示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| using System;
using System.IO;
using System.Net;
using NUnit.Framework;
using TypeMock;
namespace MockHttpWebRequest
{
public class LibraryClass
{
public string GetGoogleHomePage()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
}
[TestFixture]
[VerifyMocks]
public class UnitTests
{
private Stream responseStream = null;
private const string ExpectedResponseContent ="Content from mocked response.";
[SetUp]
public void SetUp()
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] contentAsBytes = encoding.GetBytes(ExpectedResponseContent);
this.responseStream = new MemoryStream();
this.responseStream.Write(contentAsBytes, 0, contentAsBytes.Length);
this.responseStream.Position = 0;
}
[TearDown]
public void TearDown()
{
if (responseStream != null)
{
responseStream.Dispose();
responseStream = null;
}
}
[Test(Description ="Mocks a web request using natural mocks.")]
public void NaturalMocks()
{
HttpWebRequest mockRequest = RecorderManager.CreateMockedObject<HttpWebRequest>(Constructor.Mocked);
HttpWebResponse mockResponse = RecorderManager.CreateMockedObject<HttpWebResponse>(Constructor.Mocked);
using (RecordExpectations recorder = RecorderManager.StartRecording())
{
WebRequest.Create("http://www.google.com");
recorder.CheckArguments();
recorder.Return(mockRequest);
mockRequest.GetResponse();
recorder.Return(mockResponse);
mockResponse.GetResponseStream();
recorder.Return(this.responseStream);
}
LibraryClass testObject = new LibraryClass();
string result = testObject.GetGoogleHomePage();
Assert.AreEqual(ExpectedResponseContent, result);
}
[Test(Description ="Mocks a web request using reflective mocks.")]
public void ReflectiveMocks()
{
Mock<HttpWebRequest> mockRequest = MockManager.Mock<HttpWebRequest>(Constructor.Mocked);
MockObject<HttpWebResponse> mockResponse = MockManager.MockObject<HttpWebResponse>(Constructor.Mocked);
mockResponse.ExpectAndReturn("GetResponseStream", this.responseStream);
mockRequest.ExpectAndReturn("GetResponse", mockResponse.Object);
LibraryClass testObject = new LibraryClass();
string result = testObject.GetGoogleHomePage();
Assert.AreEqual(ExpectedResponseContent, result);
}
}
} |