サードパーティのサーバーからjsonの結果を取得する統合テストがあります。それは本当にシンプルで素晴らしい作品です。
このサーバーへの実際のヒットを停止し、Moq
(またはninjectなどのモッキングライブラリ)を使用してハイジャックし、結果を強制的に返すことを望んでいました。
これは可能ですか?
以下にサンプルコードを示します。
_public Foo GoGetSomeJsonForMePleaseKThxBai()
{
// prep stuff ...
// Now get json please.
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("Http://some.fancypants.site/api/hiThere);
httpWebRequest.Method = WebRequestMethods.Http.Get;
string responseText;
using (var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
json = streamReader.ReadToEnd().ToLowerInvariant();
}
}
// Check the value of the json... etc..
}
_
そしてもちろん、このメソッドは私のテストから呼び出されます。
このメソッド(またはクラスのプロパティ)にhttpWebResponse
などのモックを渡す必要があるかもしれないと考えていましたが、これが方法であるかどうかはあまりわかりませんでした。また、応答はhttpWebRequest.GetResponse()
メソッドからの出力です。したがって、たぶん、模擬のHttpWebRequest
?を渡す必要があるだけです。
いくつかのサンプルコードを使用した提案は、最も評価されるでしょう。
実際の実装をラップする、モック可能な要求と応答を作成するファクトリーのインターフェースを取り込むように、消費するコードを変更したい場合があります。
私は答えが受け入れられてからずっとずっと下票を得ており、元の答えは質が悪く、大きな仮定をしたことを認めています。
私の元の答えからの混乱は、4.5でHttpWebResponse
をモックできるが、以前のバージョンではモックできないという事実にあります。 4.5でのモックは、廃止されたコンストラクタも利用します。したがって、推奨される一連のアクションは、要求と応答を抽象化することです。とにかく、以下はMoq 4.2で.NET 4.5を使用した完全な動作テストです。
[Test]
public void Create_should_create_request_and_respond_with_stream()
{
// arrange
var expected = "response content";
var expectedBytes = Encoding.UTF8.GetBytes(expected);
var responseStream = new MemoryStream();
responseStream.Write(expectedBytes, 0, expectedBytes.Length);
responseStream.Seek(0, SeekOrigin.Begin);
var response = new Mock<HttpWebResponse>();
response.Setup(c => c.GetResponseStream()).Returns(responseStream);
var request = new Mock<HttpWebRequest>();
request.Setup(c => c.GetResponse()).Returns(response.Object);
var factory = new Mock<IHttpWebRequestFactory>();
factory.Setup(c => c.Create(It.IsAny<string>()))
.Returns(request.Object);
// act
var actualRequest = factory.Object.Create("http://www.google.com");
actualRequest.Method = WebRequestMethods.Http.Get;
string actual;
using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse())
{
using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
actual = streamReader.ReadToEnd();
}
}
// assert
actual.Should().Be(expected);
}
public interface IHttpWebRequestFactory
{
HttpWebRequest Create(string uri);
}
以前のバージョン(少なくとも3.5まで)で機能する抽象化のより安全な最低限の実装を次に示します。
[Test]
public void Create_should_create_request_and_respond_with_stream()
{
// arrange
var expected = "response content";
var expectedBytes = Encoding.UTF8.GetBytes(expected);
var responseStream = new MemoryStream();
responseStream.Write(expectedBytes, 0, expectedBytes.Length);
responseStream.Seek(0, SeekOrigin.Begin);
var response = new Mock<IHttpWebResponse>();
response.Setup(c => c.GetResponseStream()).Returns(responseStream);
var request = new Mock<IHttpWebRequest>();
request.Setup(c => c.GetResponse()).Returns(response.Object);
var factory = new Mock<IHttpWebRequestFactory>();
factory.Setup(c => c.Create(It.IsAny<string>()))
.Returns(request.Object);
// act
var actualRequest = factory.Object.Create("http://www.google.com");
actualRequest.Method = WebRequestMethods.Http.Get;
string actual;
using (var httpWebResponse = actualRequest.GetResponse())
{
using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
actual = streamReader.ReadToEnd();
}
}
// assert
actual.Should().Be(expected);
}
public interface IHttpWebRequest
{
// expose the members you need
string Method { get; set; }
IHttpWebResponse GetResponse();
}
public interface IHttpWebResponse : IDisposable
{
// expose the members you need
Stream GetResponseStream();
}
public interface IHttpWebRequestFactory
{
IHttpWebRequest Create(string uri);
}
// barebones implementation
private class HttpWebRequestFactory : IHttpWebRequestFactory
{
public IHttpWebRequest Create(string uri)
{
return new WrapHttpWebRequest((HttpWebRequest)WebRequest.Create(uri));
}
}
public class WrapHttpWebRequest : IHttpWebRequest
{
private readonly HttpWebRequest _request;
public WrapHttpWebRequest(HttpWebRequest request)
{
_request = request;
}
public string Method
{
get { return _request.Method; }
set { _request.Method = value; }
}
public IHttpWebResponse GetResponse()
{
return new WrapHttpWebResponse((HttpWebResponse)_request.GetResponse());
}
}
public class WrapHttpWebResponse : IHttpWebResponse
{
private WebResponse _response;
public WrapHttpWebResponse(HttpWebResponse response)
{
_response = response;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (_response != null)
{
((IDisposable)_response).Dispose();
_response = null;
}
}
}
public Stream GetResponseStream()
{
return _response.GetResponseStream();
}
}
HttpWebResponseをモックアウトするのではなく、呼び出しをインターフェイスの背後にラップし、そのインターフェイスをモックします。
あなたがテストしている場合、Webレスポンスが私が望むサイトにヒットするかどうかは、クラスAがWebResponseインターフェースを呼び出して必要なデータを取得する場合とは異なるテストです。
インターフェイスをモックするには、 Rhino mocks を好みます。使い方については こちら をご覧ください。
役立つ場合は、Moqの代わりにNSubstituteを使用して、受け入れられた回答に示されているコードを見つけてください。
using NSubstitute; /*+ other assemblies*/
[TestMethod]
public void Create_should_create_request_and_respond_with_stream()
{
//Arrange
var expected = "response content";
var expectedBytes = Encoding.UTF8.GetBytes(expected);
var responseStream = new MemoryStream();
responseStream.Write(expectedBytes, 0, expectedBytes.Length);
responseStream.Seek(0, SeekOrigin.Begin);
var response = Substitute.For<HttpWebResponse>();
response.GetResponseStream().Returns(responseStream);
var request = Substitute.For<HttpWebRequest>();
request.GetResponse().Returns(response);
var factory = Substitute.For<IHttpWebRequestFactory>();
factory.Create(Arg.Any<string>()).Returns(request);
//Act
var actualRequest = factory.Create("http://www.google.com");
actualRequest.Method = WebRequestMethods.Http.Get;
string actual;
using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse())
{
using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
actual = streamReader.ReadToEnd();
}
}
//Assert
Assert.AreEqual(expected, actual);
}
public interface IHttpWebRequestFactory
{
HttpWebRequest Create(string uri);
}
単体テストが実行され、正常に合格します。
これを効果的に行う方法を私はしばらく探していた答えに与えられた賛成票。
マイクロソフトのHTTPスタックは、単体テストと分離を念頭に置いて開発されたものではありません。
次の3つのオプションがあります。
HttpWebResponse
とHttpWebRequest
を他の2つのクラスでラップします。これは、MVCチームがHttpContext
で行ったことです。2番目のオプション:
interface IWebCaller
{
string CallWeb(string address);
}
実際には、モックせずにHttpWebResponse
を返すことができます。私の答え here を参照してください。 「外部」プロキシインターフェイスは不要で、「標準」WebRequest
WebResponse
およびICreateWebRequest
のみが必要です。
HttpWebResponse
にアクセスする必要がなく、WebResponse
だけを処理できる場合はさらに簡単です。ユニットテストでこれを行い、「プレハブ」コンテンツレスポンスを消費用に返します。実際のHTTPステータスコードを返すために、「余分な距離を行く」必要がありました。 404応答ではHttpWebResponse
を使用する必要があるため、StatusCode
プロパティなどにアクセスできます。
すべてがHttpWebXXX
であると仮定する他のソリューションは、HTTPを除くWebRequest.Create()
でサポートされるすべてを無視します。使用したい登録済みプレフィックス(WebRequest.RegisterPrefix()
を使用し、それを無視すると、他のコンテンツストリームを公開する優れた方法であるため、見逃してしまいます。たとえば、Embeded Resourceストリーム、ファイルストリームなど.
また、WebRequest.Create()
の戻り値をHttpWebRequest
に明示的にキャストすると、breakageへのパスになります。はWebRequest
であり、再び、そのAPIが実際にどのように機能するかについての無知を示しています。