Last active
September 22, 2020 15:59
-
-
Save Keboo/a6b0a43aa023e31ea3278a2836591511 to your computer and use it in GitHub Desktop.
In memory testing of HttpClient
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Example.HttpClient.Tests | |
{ | |
public interface IHttpCall | |
{ | |
int InvocationsCount { get; } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Net.Http; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace Example.HttpClient.Tests | |
{ | |
public interface IHttpMessageHandler | |
{ | |
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Reflection; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace Example.HttpClient.Tests | |
{ | |
public class MemoryHttpHandler : IHttpMessageHandler | |
{ | |
private readonly List<MessageHandler> _Handlers = new List<MessageHandler>(); | |
public IHttpCall SetupPut(string url, string response, params (string key, string value)[] formData) | |
{ | |
var handler = new MessageHandler(async message => | |
{ | |
if (message.RequestUri.AbsoluteUri == url && | |
message.Method == HttpMethod.Put) | |
{ | |
var expectedContent = new FormUrlEncodedContent( | |
formData.Select(tuple => new KeyValuePair<string, string>(tuple.key, tuple.value))); | |
string expectedString = await expectedContent.ReadAsStringAsync(); | |
return string.Equals(expectedString, await message.Content.ReadAsStringAsync()); | |
} | |
return false; | |
}, | |
new HttpResponseMessage(HttpStatusCode.OK) | |
{ | |
Content = new StringContent(response) | |
}); | |
_Handlers.Add(handler); | |
return handler; | |
} | |
public IHttpCall SetupGet(string url, string response) | |
{ | |
var handler = new MessageHandler(message => | |
Task.FromResult(message.RequestUri.AbsoluteUri == url && | |
message.Method == HttpMethod.Get), | |
new HttpResponseMessage(HttpStatusCode.OK) | |
{ | |
Content = new StringContent(response) | |
}); | |
_Handlers.Add(handler); | |
return handler; | |
} | |
public IHttpCall SetupPost(string url, string response, params (string key, string value)[] formData) | |
{ | |
var handler = new MessageHandler(async message => | |
{ | |
if (message.RequestUri.AbsoluteUri == url && | |
message.Method == HttpMethod.Post) | |
{ | |
var expectedContent = new FormUrlEncodedContent( | |
formData.Select(tuple => new KeyValuePair<string, string>(tuple.key, tuple.value))); | |
string expectedString = await expectedContent.ReadAsStringAsync(); | |
return string.Equals(expectedString, await message.Content.ReadAsStringAsync()); | |
} | |
return false; | |
}, | |
new HttpResponseMessage(HttpStatusCode.OK) | |
{ | |
Content = new StringContent(response) | |
}); | |
_Handlers.Add(handler); | |
return handler; | |
} | |
public IHttpCall ThrowOn<TException>(string url) where TException : Exception, new() | |
{ | |
return ThrowOn(typeof(TException), url); | |
} | |
public IHttpCall ThrowOn(Type exceptionType, string url) | |
{ | |
if (exceptionType is null) throw new ArgumentNullException(nameof(exceptionType)); | |
if (url is null) throw new ArgumentNullException(nameof(url)); | |
if (!typeof(Exception).IsAssignableFrom(exceptionType)) | |
{ | |
throw new ArgumentException($"{exceptionType.FullName} does not derive from {typeof(Exception).FullName}", | |
nameof(exceptionType)); | |
} | |
ConstructorInfo defaultCtor = exceptionType.GetConstructors() | |
.FirstOrDefault(ctor => ctor.GetParameters().Length == 0); | |
if (defaultCtor == null) | |
{ | |
throw new ArgumentException($"Could not find default constructor on {exceptionType.FullName}", nameof(exceptionType)); | |
} | |
var handler = new MessageHandler(message => Task.FromResult(message.RequestUri.AbsoluteUri == url), | |
() => throw (Exception)defaultCtor.Invoke(new object[0])); | |
_Handlers.Add(handler); | |
return handler; | |
} | |
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
MessageHandler messageHandler = null; | |
foreach (MessageHandler handler in _Handlers) | |
{ | |
if (await handler.Predicate(request)) | |
{ | |
messageHandler = handler; | |
break; | |
} | |
} | |
if (messageHandler is null) | |
{ | |
throw new InvalidOperationException( | |
$"Could not find handler for http request '{request.RequestUri}'"); | |
} | |
messageHandler.InvocationsCount++; | |
return messageHandler.GetResponse(); | |
} | |
private class MessageHandler : IHttpCall | |
{ | |
public MessageHandler(Func<HttpRequestMessage, Task<bool>> predicate, HttpResponseMessage response) | |
: this(predicate, () => response) | |
{ } | |
public MessageHandler(Func<HttpRequestMessage, Task<bool>> predicate, Func<HttpResponseMessage> response) | |
{ | |
Predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); | |
GetResponse = response ?? throw new ArgumentNullException(nameof(response)); | |
} | |
public Func<HttpRequestMessage, Task<bool>> Predicate { get; } | |
public Func<HttpResponseMessage> GetResponse { get; } | |
public int InvocationsCount { get; set; } | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net.Http; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace Example.HttpClient.Tests | |
{ | |
public sealed class MockHttpMessageHandler : HttpMessageHandler | |
{ | |
private readonly IHttpMessageHandler _Handler; | |
public MockHttpMessageHandler(IHttpMessageHandler handler) | |
{ | |
_Handler = handler ?? throw new ArgumentNullException(nameof(handler)); | |
} | |
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | |
CancellationToken cancellationToken) | |
=> _Handler.SendAsync(request, cancellationToken); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
using Xunit; | |
namespace Example.HttpClient.Tests | |
{ | |
public class SampleTests | |
{ | |
private HttpClient Client { get; } | |
private MemoryHttpHandler Handler { get; } | |
public SampleTests() | |
{ | |
Handler = new MemoryHttpHandler(); | |
Client = new HttpClient(new MockHttpMessageHandler(Handler)); | |
} | |
[Fact] | |
public async Task SampleGetTest() | |
{ | |
// Arrange | |
string expectedResponse = "{ FirstName: \"John\", LastName: \"Doe\" }"; | |
IHttpCall call = Handler.SetupGet("https://mydomain.com/endpoint", expectedResponse); | |
// Act | |
HttpResponseMessage response = await Client.GetAsync("https://mydomain.com/endpoint"); | |
// Assert | |
Assert.True(response.IsSuccessStatusCode); | |
Assert.Equal(expectedResponse, await response.Content.ReadAsStringAsync()); | |
Assert.Equal(1, call.InvocationsCount); | |
} | |
[Fact] | |
public async Task SamplePostTest() | |
{ | |
// Arrange | |
string expectedResponse = "{ FirstName: \"John\", LastName: \"Doe\" }"; | |
IHttpCall call = Handler.SetupPost("https://mydomain.com/login", expectedResponse, ("username", "user1"), ("password", "P@ssw0rd!")); | |
// Act | |
var formContent = new FormUrlEncodedContent(new[] | |
{ | |
new KeyValuePair<string, string>("username", "user1"), | |
new KeyValuePair<string, string>("password", "P@ssw0rd!") | |
}); | |
HttpResponseMessage response = await Client.PostAsync("https://mydomain.com/login", formContent); | |
// Assert | |
Assert.True(response.IsSuccessStatusCode); | |
Assert.Equal(expectedResponse, await response.Content.ReadAsStringAsync()); | |
Assert.Equal(1, call.InvocationsCount); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment