Skip to content

Instantly share code, notes, and snippets.

@Keboo
Last active September 22, 2020 15:59
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 Keboo/a6b0a43aa023e31ea3278a2836591511 to your computer and use it in GitHub Desktop.
Save Keboo/a6b0a43aa023e31ea3278a2836591511 to your computer and use it in GitHub Desktop.
In memory testing of HttpClient
namespace Example.HttpClient.Tests
{
public interface IHttpCall
{
int InvocationsCount { get; }
}
}
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);
}
}
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; }
}
}
}
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);
}
}
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