Previously I showed how to mock SendAsync method in HttpClient so it can be unit tested. In this post, I will show how to mock the SendAsync method in DelegatingHandler so it can be unit tested as it allows you to mock the response and your test does not need to make a real Http request.
The first problem we have is the SendAsync method in DelegatingHandler is a protected internal method. This means this method can only be accessible from the current assembly or from types that are derived from the containing class, so you cannot actually call it in your unit test. To get around this, you need to expose a public method in your custom DelegatingHandler which calls the SendAsync and you’ll call that method in your test.
public class OutgoingHttpClientLoggingHandler : DelegatingHandler { // This method is only for unit testing public Task<HttpResponseMessage> SendAsyncMock(HttpRequestMessage request, CancellationToken cancellationToken) { return SendAsync(request, cancellationToken); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var body = request.Content == null ? null : await request.Content.ReadAsStringAsync(); var response = await base.SendAsync(request, cancellationToken); var responseContent = response.Content == null ? null : await response.Content.ReadAsStringAsync(); var log = new { Url = request.RequestUri.AbsoluteUri, Headers = JsonConvert.SerializeObject(request.Headers.ToDictionary(h => h.Key, h => h.Value)), Body = body, Method = request.Method.Method, StatusCode = response.StatusCode, ReasonPhrase = response.ReasonPhrase, Response = responseContent, RequestDate = DateTime.UtcNow }; _ = Task.Run(async () => // Use Task.Run because we want to fire and forget and we don't want logging to make the call takes longer to run { try { // Log request and response here i.e Send message to queue, or log to a remote server } catch (Exception exception) { // Do nothing or log why the request log had an exception } }); return response; } }
The second issue is we don’t want to actually make real web request. To achieve this, I add a fake DelegatingHandler which returns an OK HttpResponseMessage and set this to be the inner handler of the delegating handler under test.
The fake DelegatingHandler looks like
public class TestHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("OK", Encoding.UTF8, "application/json") }); } }
This is what the test looks like
[TestFixture] public class OutgoingHttpClientLoggingHandlerTests { private OutgoingHttpClientLoggingHandler _handler; [SetUp] public void SetUp() { _handler = new OutgoingHttpClientLoggingHandler(); _handler.InnerHandler = new TestHandler(); } [Test] public async Task SendAsync_With_Get_Request_Should_Send_External_Request_Log_Message() { // Arrange var url = $"https://www.{Guid.NewGuid()}.com/"; var request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(url) }; // More set up and mock go here. // Act var response = await _handler.SendAsyncMock(request, It.IsAny<CancellationToken>()); // Assert // Assertion logic goes here } }