Skip to content

Instantly share code, notes, and snippets.

@JayBazuzi
Last active October 2, 2017 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JayBazuzi/eb5b5fc548d253155ad113ba60898ba8 to your computer and use it in GitHub Desktop.
Save JayBazuzi/eb5b5fc548d253155ad113ba60898ba8 to your computer and use it in GitHub Desktop.
class HtmlRequestMessageData
{
public readonly string Content;
public readonly HttpMethod Method;
public readonly Uri RequestUri;
[JsonConstructor]
HtmlRequestMessageData(HttpMethod method, Uri requestUri, string content)
{
this.Method = method;
this.RequestUri = requestUri;
this.Content = content;
}
public static async Task<HtmlRequestMessageData> FromHttpRequestMessage(HttpRequestMessage request)
{
return new HtmlRequestMessageData(request.Method, request.RequestUri,
await (request.Content?.ReadAsStringAsync()).OrDefault());
}
}
class HtmlResponseMessageData
{
public readonly string Content;
public readonly string ReasonPhrase;
public readonly HttpStatusCode StatusCode;
[JsonConstructor]
public HtmlResponseMessageData(string content, HttpStatusCode statusCode, string reasonPhrase)
{
this.Content = content;
this.StatusCode = statusCode;
this.ReasonPhrase = reasonPhrase;
}
public static async Task<HtmlResponseMessageData> FromHttpResponseMessage(HttpResponseMessage response)
{
return new HtmlResponseMessageData(await (response.Content?.ReadAsStringAsync()).OrDefault(),
response.StatusCode,
response.ReasonPhrase);
}
public async Task<HttpResponseMessage> ToHttpResponseMessage()
{
return new HttpResponseMessage
{
Content = new StringContent(this.Content),
ReasonPhrase = this.ReasonPhrase,
StatusCode = this.StatusCode
};
}
}
[TestClass]
public class LiveIntegrationTest : TestBase
{
readonly PassthroughAndRecordHttpMessageHandler _recordingHandler = new PassthroughAndRecordHttpMessageHandler();
public TestContext TestContext { get; set; }
[TestCleanup]
public void TestCleanup()
{
var json = JsonConvert.SerializeObject(this._recordingHandler.Recordings, Formatting.Indented);
RecordPlaybackTestUtilities.SaveRecordings(TestContext, json);
}
protected override HttpClientHandler GetHandler()
{
return this._recordingHandler;
}
}
class PassthroughAndRecordHttpMessageHandler : HttpClientHandler
{
public readonly List<Tuple<HtmlRequestMessageData, HtmlResponseMessageData>> Recordings =
new List<Tuple<HtmlRequestMessageData, HtmlResponseMessageData>>();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
await Record(request, response);
return response;
}
async Task Record(HttpRequestMessage request, HttpResponseMessage response)
{
this.Recordings.Add(Tuple.Create(
await HtmlRequestMessageData.FromHttpRequestMessage(request),
await HtmlResponseMessageData.FromHttpResponseMessage(response)));
}
}
class PlaybackHtmlMessageHandler : HttpClientHandler
{
readonly List<Tuple<HtmlRequestMessageData, HtmlResponseMessageData>> _recordedRequestsAndResponses;
int _currentRecordingIndex;
public PlaybackHtmlMessageHandler(
List<Tuple<HtmlRequestMessageData, HtmlResponseMessageData>>
recordedRequestsAndResponses)
{
this._recordedRequestsAndResponses = recordedRequestsAndResponses;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
Tuple<HtmlRequestMessageData, HtmlResponseMessageData> requestAndResponse =
this._recordedRequestsAndResponses[this._currentRecordingIndex++];
requestAndResponse.Item1.Should().BeEquivalentTo(await HtmlRequestMessageData.FromHttpRequestMessage(request));
return await requestAndResponse.Item2.ToHttpResponseMessage();
}
protected override void Dispose(bool disposing)
{
VerifyAllRequestsSent();
base.Dispose(disposing);
}
public void VerifyAllRequestsSent()
{
Assert.AreEqual(this._recordedRequestsAndResponses.Count, this._currentRecordingIndex);
}
}
[TestClass]
public class PlaybackIntegrationTest : TestBase
{
PlaybackHtmlMessageHandler _playbackHandler;
public TestContext TestContext { get; set; }
[TestInitialize]
public void TestInitialize()
{
var json = RecordPlaybackTestUtilities.LoadRecordings(TestContext);
var recordings =
JsonConvert.DeserializeObject<List<Tuple<HtmlRequestMessageData, HtmlResponseMessageData>>>(json);
this._playbackHandler = new PlaybackHtmlMessageHandler(recordings);
}
[TestCleanup]
public void TestCleanup()
{
this._playbackHandler.VerifyAllRequestsSent();
}
protected override HttpClientHandler GetHandler()
{
return this._playbackHandler;
}
}
static class RecordPlaybackTestUtilities
{
static string GetRecordPath(TestContext testContext)
{
var fileName = new StackFrame(skipFrames: 0, fNeedFileInfo: true).GetFileName();
fileName.Should().NotBeNull("File name not found. Check debug info and the like.");
var directoryName = Path.GetDirectoryName(fileName);
return Path.Combine(directoryName, testContext.TestName + ".record");
}
public static void SaveRecordings(TestContext testContext, string json)
{
File.WriteAllText(GetRecordPath(testContext), json);
}
public static string LoadRecordings(TestContext testContext)
{
return File.ReadAllText(GetRecordPath(testContext));
}
}
namespace TaskExtensions
{
static class _
{
/// <summary>Turns a null Task into a Task that returns null. For example, <code>await a?.b.OrDefault()</code></summary>
/// <remarks>IMO, this is a flaw in C#. It should allow `await null` to return null.</remarks>
public static Task<T> OrDefault<T>(this Task<T> task)
{
return task ?? Task.FromResult(default(T));
}
}
}
public abstract class TestBase
{
protected abstract HttpClientHandler GetHandler();
[TestMethod]
public async Task GetWeatherByZipCode()
{
var httpClient = new HttpClient(GetHandler());
var result = await httpClient.GetStringAsync("http://samples.openweathermap.org/data/2.5/weather?zip=94040,us&appid=b1b15e88fa797225412429c1c50c122a1");
ApprovalTests.Approvals.Verify(result);
JsonConvert.DeserializeObject(result).Should().BeEquivalentTo(JsonConvert.DeserializeObject(@"{
""coord"": {
""lon"": -122.08,
""lat"": 37.39
},
""weather"": [
{
""id"": 500,
""main"": ""Rain"",
""description"": ""light rain"",
""icon"": ""10n""
}
],
""base"": ""stations"",
""main"": {
""temp"": 277.14,
""pressure"": 1025,
""humidity"": 86,
""temp_min"": 275.15,
""temp_max"": 279.15
},
""visibility"": 16093,
""wind"": {
""speed"": 1.67,
""deg"": 53.0005
},
""clouds"": { ""all"": 1 },
""dt"": 1485788160,
""sys"": {
""type"": 1,
""id"": 471,
""message"": 0.0116,
""country"": ""US"",
""sunrise"": 1485789140,
""sunset"": 1485826300
},
""id"": 5375480,
""name"": ""Mountain View"",
""cod"": 200
}"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment