Reputation: 4376
How do I unit test a custom DelegatingHandler? I have the following but its complaining the innerHandler not set.
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://foo.com");
var handler = new FooHandler()
{
InnerHandler = new FooHandler()
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(httpRequestMessage, new CancellationToken());
Assert.That(result.Headers.GetValues("some-header").First(), Is.Not.Empty, "");
Upvotes: 32
Views: 14408
Reputation: 192406
You can set the InnerHandler
property of the DelegatingHandler you're testing (FooHandler
) with a dummy/fake handler (TestHandler
) as shown in that linked post in your comment.
public class TestHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}
// in your test class method
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com/");
var handler = new FooHandler()
{
InnerHandler = new TestHandler() // <-- change to use this
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(httpRequestMessage, new CancellationToken());
Assert.That(result.Headers.GetValues("some-header").First(), Is.Not.Empty, "");
Unlike that post, this should be the minimum you need to set up to get your test to run.
Upvotes: 44
Reputation: 5125
You just need to wire FooHandler
's InnerHandler
to an instance of type HttpMessageHandler
.
You can mock using AutoFixture
as fixture.Create<HttpMessageHandler>()
, or you can handle-roll your own, or you can use the awesome MockHttpMessageHandler
.
TLDR
There are a few small things off with the answers here so I thought of adding my answer. I recall seeing similar code as mentioned in Ray's answer, and I was thrown off by the usage of Task.Factory.StartNew()
in there, which is totally unnecessary. Moq.Protected
is a bit brittle for my taste; it's ok in this situation as "SendAsync"
method won't change mostly.
The Delegating Handler follows the chain-of-responsibility pattern where one can form a chain. One doesn't get to control the last handler which will be HttpMessageHandler
, an abstract class.
So, the simplest form of chain is,
HttpClient -> HttpMessageHandler impl
In your case, the chain is,
HttpMessageInvoker -> FooHandler -> HttpMessageHandler impl
It's convenient to use the plumbing class HttpMessageInvoker
for testing, instead of the porcelain class HttpClient
. In your example, HttpMessageInvoker
ctor handles the part of wiring up its InnerHandler
with FooHandler
, but you need to wire up FooHandler
's InnerHandler
. Note that one doesn't need to do this for HttpClient
as there exists a helper method, HttpClientFactory.Create()
which handles the wiring. But there isn't an HttpMessageInvokerFactory.Create()
method. I have few tests in my repo where I have scenarios with few handlers in the chain, and I eventually ended up writing that helper method (pasted below).
Note that only HttpMessageInvokerFactory
handles the wiring part; with others, one has to do it explicitly.
Examples
Putting it all together, let's say one has a simple Delegating Handler to test as RelayHandler
which simply relays the http request,
/// <summary>
/// Simply relays request, just for fun.
/// </summary>
public class RelayHandler : DelegatingHandler
{
/// <inheritdoc cref="RelayHandler" />
public RelayHandler()
{ }
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}
One can test it as,
[Test]
public async Task Relays()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var relayHandler = new RelayHandler();
using var invoker = HttpMessageInvokerFactory.Create(
fixture.Create<HttpMessageHandler>(), relayHandler);
using var requestMessage = fixture.Create<HttpRequestMessage>();
using var response = await invoker.SendAsync(requestMessage, CancellationToken.None);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
where HttpMessageInvokerFactory
is as,
public static class HttpMessageInvokerFactory
{
public static HttpMessageInvoker Create(
params DelegatingHandler[] handlers)
{
return Create(null!, handlers);
}
public static HttpMessageInvoker Create(
HttpMessageHandler innerHandler,
params DelegatingHandler[] handlers)
{
if (handlers == null || !handlers.Any())
{
throw new ArgumentNullException(nameof(handlers));
}
if (handlers.Any(x => x == null))
{
throw new ArgumentNullException(nameof(handlers), "At least one of the handlers is null.");
}
var first = handlers[0];
Array.Reverse(handlers);
var current = innerHandler;
foreach (var next in handlers)
{
if (current != null)
{
next.InnerHandler = current;
}
current = next;
}
return new HttpMessageInvoker(first);
}
}
The reason why FooHandler
's InnerHandler
needs to be setup is because its InnerHandler
is executed is my guess in your test. That said, one could have a Delegating Handler that "short-circuits" the call, and one doesn't need its InnerHandler
.
Here is a Delegating Handler, ThrowingHandler
that throws unconditionally.
/// <summary>
/// Throws an exception.
/// </summary>
public class ThrowingHandler : DelegatingHandler
{
private readonly Exception exception;
/// <inheritdoc cref="ThrowingHandler" />
public ThrowingHandler(
Exception exception)
{
// funny, no?
this.exception = exception
?? throw new ArgumentNullException(nameof(exception));
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
throw exception;
}
}
I don't need to setup its InnerHandler
in test as it's not used. :shrug: One could if they feel like to have a consistent test setup.
[Test]
public void Throws()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var throwingHandler = new ThrowingHandler(new TurnDownForWhatException());
using var invoker = HttpMessageInvokerFactory.Create(
throwingHandler);
using var requestMessage = fixture.Create<HttpRequestMessage>();
var ex = Assert.ThrowsAsync<TurnDownForWhatException>(async () =>
{
using var _ = await invoker.SendAsync(requestMessage, CancellationToken.None);
});
}
// TurnDownForWhatException is a custom exception
Note that the TestHandler
in Ray's answer is better named as HttpStatusOkHandler
. But, one could need a handler to spoof an Http 400 instead, and so on.
public class HttpStatusOkHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}
So, eventually I came up with below.
/// <summary>
/// Short-circuits with a canned http response.
/// </summary>
/// <remarks>
/// Canned response should not be disposed if used with a retry handler
/// as it's meant for multiuse. If so, consider using <see cref="ShortCircuitingResponseHandler"/>
/// instead.
/// </remarks>
public class ShortCircuitingCannedResponseHandler : DelegatingHandler
{
private readonly HttpResponseMessage response;
/// <inheritdoc cref="ShortCircuitingCannedResponseHandler" />
public ShortCircuitingCannedResponseHandler(
HttpResponseMessage response)
{
this.response = response
?? throw new ArgumentNullException(nameof(response));
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.FromResult(response);
}
}
Upvotes: 1
Reputation: 706
With using Moq.Protected;
, you can get more control over the response outcome.
var request = new HttpRequestMessage();
var innerHandlerMock = new Mock<DelegatingHandler>(MockBehavior.Strict);
innerHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", request, ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
var handler = new FooHandler()
{
InnerHandler = innerHandlerMock.Object
};
var invoker = new HttpMessageInvoker(handler);
// act
await invoker.SendAsync(request, default);
Upvotes: 9
Reputation: 6100
Use e.g. System.Net.Http.HttpClientHandler or System.Web.Http.HttpServer as an InnerHandler:
var handler = new FooHandler()
{
InnerHandler = new System.Net.Http.HttpClientHandler()
};
or
var handler = new FooHandler()
{
InnerHandler = new System.Web.Http.HttpServer()
};
Upvotes: 0