From 3038661f6d6ad4854c20d66f3fcc9514be744f5a Mon Sep 17 00:00:00 2001 From: Alexandru Macocian Date: Thu, 13 May 2021 13:55:17 +0200 Subject: [PATCH] Mockable http client implementation. --- .../Http/HttpClient.cs | 217 ++++++++++++ .../Http/HttpClientEventMessage.cs | 16 + .../Http/IHttpClient.cs | 49 +++ .../SystemExtensions.NetStandard.csproj | 2 +- SystemExtensionsTests/Http/HttpClientTests.cs | 321 ++++++++++++++++++ .../SystemExtensionsTests.csproj | 5 +- .../Utils/MockHttpMessageHandler.cs | 29 ++ SystemExtensionsTests/app.config | 12 +- 8 files changed, 643 insertions(+), 8 deletions(-) create mode 100644 SystemExtensions.NetStandard/Http/HttpClient.cs create mode 100644 SystemExtensions.NetStandard/Http/HttpClientEventMessage.cs create mode 100644 SystemExtensions.NetStandard/Http/IHttpClient.cs create mode 100644 SystemExtensionsTests/Http/HttpClientTests.cs create mode 100644 SystemExtensionsTests/Utils/MockHttpMessageHandler.cs diff --git a/SystemExtensions.NetStandard/Http/HttpClient.cs b/SystemExtensions.NetStandard/Http/HttpClient.cs new file mode 100644 index 0000000..ecf2bef --- /dev/null +++ b/SystemExtensions.NetStandard/Http/HttpClient.cs @@ -0,0 +1,217 @@ +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Http +{ + public sealed class HttpClient : IHttpClient, IDisposable + { + private readonly HttpClient httpClient; + private readonly Type scope; + private EventHandler eventEmitted; + + public event EventHandler EventEmitted + { + add + { + this.eventEmitted += value; + } + remove + { + this.eventEmitted -= value; + } + } + public Uri BaseAddress { get => this.httpClient.BaseAddress; set => this.httpClient.BaseAddress = value; } + public HttpRequestHeaders DefaultRequestHeaders => this.httpClient.DefaultRequestHeaders; + public long MaxResponseContentBufferSize { get => this.httpClient.MaxResponseContentBufferSize; set => this.httpClient.MaxResponseContentBufferSize = value; } + public TimeSpan Timeout { get => this.httpClient.Timeout; set => this.httpClient.Timeout = value; } + + public HttpClient() + { + this.httpClient = new HttpClient(); + this.scope = typeof(Tscope); + } + + public HttpClient( + HttpMessageHandler handler) + { + this.httpClient = new HttpClient(handler); + this.scope = typeof(Tscope); + } + + public HttpClient( + HttpMessageHandler handler, + bool disposeHandler) + { + this.httpClient = new HttpClient(handler, disposeHandler); + this.scope = typeof(Tscope); + } + + public void CancelPendingRequests() + { + this.LogInformation(string.Empty, "Canceling request"); + this.httpClient.CancelPendingRequests(); + } + public Task DeleteAsync(string requestUri) + { + this.LogInformation(requestUri, nameof(DeleteAsync)); + return this.httpClient.DeleteAsync(requestUri); + } + public Task DeleteAsync(string requestUri, CancellationToken cancellationToken) + { + this.LogInformation(requestUri, nameof(DeleteAsync)); + return this.httpClient.DeleteAsync(requestUri, cancellationToken); + } + public Task DeleteAsync(Uri requestUri) + { + this.LogInformation(requestUri.ToString(), nameof(DeleteAsync)); + return this.httpClient.DeleteAsync(requestUri); + } + public Task DeleteAsync(Uri requestUri, CancellationToken cancellationToken) + { + this.LogInformation(requestUri.ToString(), nameof(DeleteAsync)); + return this.httpClient.DeleteAsync(requestUri, cancellationToken); + } + public Task GetAsync(string requestUri) + { + this.LogInformation(requestUri, nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri); + } + public Task GetAsync(string requestUri, HttpCompletionOption completionOption) + { + this.LogInformation(requestUri, nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, completionOption); + } + public Task GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken) + { + this.LogInformation(requestUri, nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken); + } + public Task GetAsync(string requestUri, CancellationToken cancellationToken) + { + this.LogInformation(requestUri, nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, cancellationToken); + } + public Task GetAsync(Uri requestUri) + { + this.LogInformation(requestUri.ToString(), nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri); + } + public Task GetAsync(Uri requestUri, HttpCompletionOption completionOption) + { + this.LogInformation(requestUri.ToString(), nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, completionOption); + } + public Task GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken) + { + this.LogInformation(requestUri.ToString(), nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken); + } + public Task GetAsync(Uri requestUri, CancellationToken cancellationToken) + { + this.LogInformation(requestUri.ToString(), nameof(GetAsync)); + return this.httpClient.GetAsync(requestUri, cancellationToken); + } + public Task GetByteArrayAsync(string requestUri) + { + this.LogInformation(requestUri, nameof(GetByteArrayAsync)); + return this.httpClient.GetByteArrayAsync(requestUri); + } + public Task GetByteArrayAsync(Uri requestUri) + { + this.LogInformation(requestUri.ToString(), nameof(GetByteArrayAsync)); + return this.httpClient.GetByteArrayAsync(requestUri); + } + public Task GetStreamAsync(string requestUri) + { + this.LogInformation(requestUri, nameof(GetStreamAsync)); + return this.httpClient.GetStreamAsync(requestUri); + } + public Task GetStreamAsync(Uri requestUri) + { + this.LogInformation(requestUri.ToString(), nameof(GetStreamAsync)); + return this.httpClient.GetStreamAsync(requestUri); + } + public Task GetStringAsync(string requestUri) + { + this.LogInformation(requestUri, nameof(GetStringAsync)); + return this.httpClient.GetStringAsync(requestUri); + } + public Task GetStringAsync(Uri requestUri) + { + this.LogInformation(requestUri.ToString(), nameof(GetStringAsync)); + return this.httpClient.GetStringAsync(requestUri); + } + public Task PostAsync(string requestUri, HttpContent content) + { + this.LogInformation(requestUri, nameof(PostAsync)); + return this.httpClient.PostAsync(requestUri, content); + } + public Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken) + { + this.LogInformation(requestUri, nameof(PostAsync)); + return this.httpClient.PostAsync(requestUri, content, cancellationToken); + } + public Task PostAsync(Uri requestUri, HttpContent content) + { + this.LogInformation(requestUri.ToString(), nameof(PostAsync)); + return this.httpClient.PostAsync(requestUri, content); + } + public Task PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken) + { + this.LogInformation(requestUri.ToString(), nameof(PostAsync)); + return this.httpClient.PostAsync(requestUri, content, cancellationToken); + } + public Task PutAsync(string requestUri, HttpContent content) + { + this.LogInformation(requestUri, nameof(PutAsync)); + return this.httpClient.PutAsync(requestUri, content); + } + public Task PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken) + { + this.LogInformation(requestUri, nameof(PutAsync)); + return this.httpClient.PutAsync(requestUri, content, cancellationToken); + } + public Task PutAsync(Uri requestUri, HttpContent content) + { + this.LogInformation(requestUri.ToString(), nameof(PutAsync)); + return this.httpClient.PutAsync(requestUri, content); + } + public Task PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken) + { + this.LogInformation(requestUri.ToString(), nameof(PutAsync)); + return this.httpClient.PutAsync(requestUri, content, cancellationToken); + } + public Task SendAsync(HttpRequestMessage request) + { + this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync)); + return this.httpClient.SendAsync(request); + } + public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption) + { + this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync)); + return this.httpClient.SendAsync(request, completionOption); + } + public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) + { + this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync)); + return this.httpClient.SendAsync(request, completionOption, cancellationToken); + } + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync)); + return this.httpClient.SendAsync(request, cancellationToken); + } + public void Dispose() + { + this.httpClient.Dispose(); + } + + private void LogInformation(string url, string method) + { + this.eventEmitted?.Invoke(this, new HttpClientEventMessage(this.scope, method, url)); + } + } +} diff --git a/SystemExtensions.NetStandard/Http/HttpClientEventMessage.cs b/SystemExtensions.NetStandard/Http/HttpClientEventMessage.cs new file mode 100644 index 0000000..2393989 --- /dev/null +++ b/SystemExtensions.NetStandard/Http/HttpClientEventMessage.cs @@ -0,0 +1,16 @@ +namespace System.Http +{ + public sealed class HttpClientEventMessage + { + public Type Scope { get; } + public string Method { get; } + public string Url { get; } + + internal HttpClientEventMessage(Type scope, string method, string url) + { + this.Scope = scope; + this.Method = method; + this.Url = url; + } + } +} diff --git a/SystemExtensions.NetStandard/Http/IHttpClient.cs b/SystemExtensions.NetStandard/Http/IHttpClient.cs new file mode 100644 index 0000000..ddd7334 --- /dev/null +++ b/SystemExtensions.NetStandard/Http/IHttpClient.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Http +{ + public interface IHttpClient + { + event EventHandler EventEmitted; + Uri BaseAddress { get; set; } + HttpRequestHeaders DefaultRequestHeaders { get; } + long MaxResponseContentBufferSize { get; set; } + TimeSpan Timeout { get; set; } + + void CancelPendingRequests(); + Task DeleteAsync(string requestUri); + Task DeleteAsync(string requestUri, CancellationToken cancellationToken); + Task DeleteAsync(Uri requestUri); + Task DeleteAsync(Uri requestUri, CancellationToken cancellationToken); + Task GetAsync(string requestUri); + Task GetAsync(string requestUri, HttpCompletionOption completionOption); + Task GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken); + Task GetAsync(string requestUri, CancellationToken cancellationToken); + Task GetAsync(Uri requestUri); + Task GetAsync(Uri requestUri, HttpCompletionOption completionOption); + Task GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken); + Task GetAsync(Uri requestUri, CancellationToken cancellationToken); + Task GetByteArrayAsync(string requestUri); + Task GetByteArrayAsync(Uri requestUri); + Task GetStreamAsync(string requestUri); + Task GetStreamAsync(Uri requestUri); + Task GetStringAsync(string requestUri); + Task GetStringAsync(Uri requestUri); + Task PostAsync(string requestUri, HttpContent content); + Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); + Task PostAsync(Uri requestUri, HttpContent content); + Task PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken); + Task PutAsync(string requestUri, HttpContent content); + Task PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); + Task PutAsync(Uri requestUri, HttpContent content); + Task PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken); + Task SendAsync(HttpRequestMessage request); + Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption); + Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken); + Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); + } +} diff --git a/SystemExtensions.NetStandard/SystemExtensions.NetStandard.csproj b/SystemExtensions.NetStandard/SystemExtensions.NetStandard.csproj index 607a61b..0b0cbea 100644 --- a/SystemExtensions.NetStandard/SystemExtensions.NetStandard.csproj +++ b/SystemExtensions.NetStandard/SystemExtensions.NetStandard.csproj @@ -6,7 +6,7 @@ LICENSE true System - 1.1.4 + 1.2.0 Alexandru Macocian https://github.com/AlexMacocian/SystemExtensions diff --git a/SystemExtensionsTests/Http/HttpClientTests.cs b/SystemExtensionsTests/Http/HttpClientTests.cs new file mode 100644 index 0000000..35ae7eb --- /dev/null +++ b/SystemExtensionsTests/Http/HttpClientTests.cs @@ -0,0 +1,321 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Http; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using SystemExtensionsTests.Utils; + +namespace SystemExtensionsTests.Http +{ + [TestClass] + public class HttpClientTests + { + private const string resourceUrl = "resource"; + + private readonly Uri TestAddressUrl = new Uri("https://helloworld.xyz"); + private readonly MockHttpMessageHandler handler = new MockHttpMessageHandler(); + private IHttpClient httpClient; + + [TestInitialize] + public void TestInitialize() + { + this.httpClient = new HttpClient(handler, false) + { + BaseAddress = TestAddressUrl + }; + } + + [TestMethod] + public async Task HttpClientEmitsCorrectMessages() + { + int messagesEmitted = 0; + this.httpClient.EventEmitted += (sender, message) => + { + messagesEmitted++; + message.Scope.Should().Be(typeof(object)); + message.Method.Should().Be("GetAsync"); + message.Url.Should().Be(TestAddressUrl.ToString()); + }; + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK); + + await this.httpClient.GetAsync(TestAddressUrl); + + messagesEmitted.Should().Be(1); + } + [TestMethod] + public void SetBaseAddress() + { + this.httpClient.BaseAddress = TestAddressUrl; + this.httpClient.BaseAddress.Should().Be(TestAddressUrl); + } + [TestMethod] + public void SetDefaultRequestHeaders() + { + this.httpClient.DefaultRequestHeaders.TryAddWithoutValidation("someheader", "thisvalue"); + this.httpClient.DefaultRequestHeaders.GetValues("someheader").Should().BeEquivalentTo("thisvalue"); + } + [TestMethod] + public void SetMaxResponseContentBufferSize() + { + this.httpClient.MaxResponseContentBufferSize = 2000; + this.httpClient.MaxResponseContentBufferSize.Should().Be(2000); + } + [TestMethod] + public void SetTimeout() + { + this.httpClient.Timeout = TimeSpan.FromSeconds(1); + this.httpClient.Timeout.Should().Be(TimeSpan.FromSeconds(1)); + } + [TestMethod] + public async Task DeleteAsyncStringReturns200() + { + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK); + + var response = await this.httpClient.DeleteAsync(resourceUrl); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + public async Task DeleteAsyncUriReturns200() + { + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK); + + var response = await this.httpClient.DeleteAsync(TestAddressUrl); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + [Ignore("NetStandard2.0 implementation doesn't cancel operation when token is cancelled")] + public async Task DeleteAsyncCanceledThrowsTaskCanceledException() + { + this.SetupLongResponse(TestAddressUrl, HttpStatusCode.OK); + var cts = new CancellationTokenSource(); + + var responseTask = this.httpClient.DeleteAsync(TestAddressUrl, cts.Token); + cts.Cancel(); + + var awaitAction = new Func>(async () => + { + return await responseTask; + }); + + await awaitAction.Should().ThrowAsync(); + } + [TestMethod] + public async Task GetAsyncStringReturns200() + { + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK); + + var response = await this.httpClient.GetAsync(resourceUrl); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + public async Task GetAsyncUriReturns200() + { + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK); + + var response = await this.httpClient.GetAsync(TestAddressUrl); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + [Ignore("NetStandard2.0 implementation doesn't cancel operation when token is cancelled")] + public async Task GetAsyncCanceledThrowsTaskCanceledException() + { + this.SetupLongResponse(TestAddressUrl, HttpStatusCode.OK); + var cts = new CancellationTokenSource(); + + var responseTask = this.httpClient.GetAsync(TestAddressUrl, cts.Token); + cts.Cancel(); + + var awaitAction = new Func>(async () => + { + return await responseTask; + }); + + await awaitAction.Should().ThrowAsync(); + } + [TestMethod] + public async Task PostAsyncStringReturns200() + { + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK); + + var response = await this.httpClient.PostAsync(resourceUrl, null); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + public async Task PostAsyncUriReturns200() + { + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK); + + var response = await this.httpClient.PostAsync(TestAddressUrl, null); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + [Ignore("NetStandard2.0 implementation doesn't cancel operation when token is cancelled")] + public async Task PostAsyncCanceledThrowsTaskCanceledException() + { + this.SetupLongResponse(TestAddressUrl, HttpStatusCode.OK); + var cts = new CancellationTokenSource(); + + var responseTask = this.httpClient.PostAsync(TestAddressUrl, null, cts.Token); + cts.Cancel(); + + var awaitAction = new Func>(async () => + { + return await responseTask; + }); + + await awaitAction.Should().ThrowAsync(); + } + [TestMethod] + public async Task SendAsyncUriReturns200() + { + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK); + + var response = await this.httpClient.SendAsync(new HttpRequestMessage { RequestUri = TestAddressUrl }); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + [TestMethod] + [Ignore("NetStandard2.0 implementation doesn't cancel operation when token is cancelled")] + public async Task SendAsyncCanceledThrowsTaskCanceledException() + { + this.SetupLongResponse(TestAddressUrl, HttpStatusCode.OK); + var cts = new CancellationTokenSource(); + + var responseTask = this.httpClient.SendAsync(new HttpRequestMessage { RequestUri = TestAddressUrl }, cts.Token); + cts.Cancel(); + + var awaitAction = new Func>(async () => + { + return await responseTask; + }); + + await awaitAction.Should().ThrowAsync(); + } + [TestMethod] + public async Task GetStreamAsyncStringReturns200() + { + var ms = new MemoryStream(new byte[] { 13, 10, 11, 12 }); + var sc = new StreamContent(ms); + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetStreamAsync(resourceUrl); + + var responseBuffer = new byte[4]; + await response.ReadAsync(responseBuffer, 0, 4); + responseBuffer[0].Should().Be(13); + responseBuffer[1].Should().Be(10); + responseBuffer[2].Should().Be(11); + responseBuffer[3].Should().Be(12); + } + [TestMethod] + public async Task GetStreamAsyncUriReturns200() + { + var ms = new MemoryStream(new byte[] { 13, 10, 11, 12 }); + var sc = new StreamContent(ms); + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetStreamAsync(TestAddressUrl); + + var responseBuffer = new byte[4]; + await response.ReadAsync(responseBuffer, 0, 4); + responseBuffer[0].Should().Be(13); + responseBuffer[1].Should().Be(10); + responseBuffer[2].Should().Be(11); + responseBuffer[3].Should().Be(12); + } + [TestMethod] + public async Task GetStringAsyncStringReturns200() + { + var sc = new StringContent("hello"); + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetStringAsync(resourceUrl); + + response.Should().Be("hello"); + } + [TestMethod] + public async Task GetStringAsyncUriReturns200() + { + var sc = new StringContent("hello"); + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetStringAsync(TestAddressUrl); + + response.Should().Be("hello"); + } + [TestMethod] + public async Task GetByteArrayAsyncStringReturns200() + { + var sc = new ByteArrayContent(new byte[4] { 13, 10, 11, 12 }); + this.SetupResponse(new Uri(TestAddressUrl, resourceUrl), HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetByteArrayAsync(resourceUrl); + + response[0].Should().Be(13); + response[1].Should().Be(10); + response[2].Should().Be(11); + response[3].Should().Be(12); + } + [TestMethod] + public async Task GetByteArrayAsyncUriReturns200() + { + var sc = new ByteArrayContent(new byte[4] { 13, 10, 11, 12 }); + this.SetupResponse(TestAddressUrl, HttpStatusCode.OK, sc); + + var response = await this.httpClient.GetByteArrayAsync(TestAddressUrl); + + response[0].Should().Be(13); + response[1].Should().Be(10); + response[2].Should().Be(11); + response[3].Should().Be(12); + } + [TestMethod] + [Ignore("NetStandard2.0 implementation doesn't cancel operation when token is cancelled")] + public async Task CancelPendingRequestThrowsTaskCanceledException() + { + this.SetupLongResponse(TestAddressUrl, HttpStatusCode.OK); + + var responseTask = this.httpClient.GetAsync(TestAddressUrl); + this.httpClient.CancelPendingRequests(); + + var awaitAction = new Func>(async () => + { + return await responseTask; + }); + + await awaitAction.Should().ThrowAsync(); + } + + private void SetupResponse(Uri expectedUri, HttpStatusCode statusCode, HttpContent httpContent = null) + { + this.handler.ResponseResolver = (request) => + { + request.RequestUri.Should().Be(expectedUri); + return new HttpResponseMessage { StatusCode = statusCode, Content = httpContent }; + }; + } + private void SetupLongResponse(Uri expectedUri, HttpStatusCode statusCode, HttpContent httpContent = null) + { + this.handler.ResponseResolverAsync = async (request) => + { + request.RequestUri.Should().Be(expectedUri); + for (int i = 0; i < 10; i++) + { + await Task.Delay(100); + } + + return new HttpResponseMessage { StatusCode = statusCode, Content = httpContent }; + }; + } + } +} diff --git a/SystemExtensionsTests/SystemExtensionsTests.csproj b/SystemExtensionsTests/SystemExtensionsTests.csproj index 22244c8..bb26dc2 100644 --- a/SystemExtensionsTests/SystemExtensionsTests.csproj +++ b/SystemExtensionsTests/SystemExtensionsTests.csproj @@ -9,7 +9,7 @@ Properties SystemExtensionsTests SystemExtensionsTests - v4.7.2 + v4.8 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 @@ -51,6 +51,7 @@ + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0-preview.2.21154.6\lib\net45\System.Runtime.CompilerServices.Unsafe.dll @@ -77,10 +78,12 @@ + + diff --git a/SystemExtensionsTests/Utils/MockHttpMessageHandler.cs b/SystemExtensionsTests/Utils/MockHttpMessageHandler.cs new file mode 100644 index 0000000..b326e94 --- /dev/null +++ b/SystemExtensionsTests/Utils/MockHttpMessageHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace SystemExtensionsTests.Utils +{ + public sealed class MockHttpMessageHandler : HttpMessageHandler + { + public Func ResponseResolver { get; set; } + public Func> ResponseResolverAsync { get; set; } + + public MockHttpMessageHandler() + { + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (this.ResponseResolver is object) + { + return Task.FromResult(this.ResponseResolver(request)); + } + else + { + return this.ResponseResolverAsync(request); + } + } + } +} diff --git a/SystemExtensionsTests/app.config b/SystemExtensionsTests/app.config index 2833cc4..22087c1 100644 --- a/SystemExtensionsTests/app.config +++ b/SystemExtensionsTests/app.config @@ -1,15 +1,15 @@ - + - - + + - - + + - \ No newline at end of file +