9

Is there a way to get the contents of the http request before deciding what kind of response I want to send back for the test? Multiple tests will use this class and each test will have multiple http requests.

This code does not compile because the lambda is not async and there is an await in it. I'm new to async-await, so I'm not sure how to resolve this. I briefly considered having multiple TestHttpClientFactories, but that would mean duplicated code, so decided against it, if possible.

Any help is appreciated.

public class TestHttpClientFactory : IHttpClientFactory
{
    public HttpClient CreateClient(string name)
    {
        var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);

        messageHandlerMock.Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
            {
                HttpResponseMessage response = new HttpResponseMessage();
                var requestMessageContent = await request.Content.ReadAsStringAsync();

                // decide what to put in the response after looking at the contents of the request

                return response;
            })
            .Verifiable();

        var httpClient = new HttpClient(messageHandlerMock.Object);
        return httpClient;
    }
}
Jim Aho
  • 9,932
  • 15
  • 56
  • 87
rjacobsen0
  • 1,287
  • 13
  • 25
  • Create your own handler that exposes a delegate to handle the desired behavior – Nkosi Dec 22 '20 at 18:22
  • Nkosi, that is a good idea. If moq won't work, I'll definitely consider that. – rjacobsen0 Dec 22 '20 at 18:25
  • I should also mention that I tried var requestMessageContent = request.Content.ToString(); but got a System.NullReferenceException, so that's not a solution. – rjacobsen0 Dec 22 '20 at 18:27
  • Have a look at this answer I gave to a similar question https://stackoverflow.com/a/54227679/5233410 – Nkosi Dec 22 '20 at 18:36

1 Answers1

12

To take advantage of the async delegate use the Returns method instead

public class TestHttpClientFactory : IHttpClientFactory {
    public HttpClient CreateClient(string name) {
        var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);

        messageHandlerMock.Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
            .Returns(async (HttpRequestMessage request, CancellationToken token) => {
                
                string requestMessageContent = await request.Content.ReadAsStringAsync();

                HttpResponseMessage response = new HttpResponseMessage();

                //...decide what to put in the response after looking at the contents of the request

                return response;
            })
            .Verifiable();

        var httpClient = new HttpClient(messageHandlerMock.Object);
        return httpClient;
    }
}

Or consider creating your own handler that exposes a delegate to handle the desired behavior.

For example

public class DelegatingHandlerStub : DelegatingHandler {
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public DelegatingHandlerStub() {
        _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
    }

    public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
        _handlerFunc = handlerFunc;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _handlerFunc(request, cancellationToken);
    }
}

And used in the factory like this

public class TestHttpClientFactory : IHttpClientFactory {
    public HttpClient CreateClient(string name) {
        var messageHandlerMock = new DelegatingHandlerStub(async (HttpRequestMessage request, CancellationToken token) => {
                
            string requestMessageContent = await request.Content.ReadAsStringAsync();

            HttpResponseMessage response = new HttpResponseMessage();

            //...decide what to put in the response after looking at the contents of the request

            return response;
        });

        var httpClient = new HttpClient(messageHandlerMock);
        return httpClient;
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thanks. I decided to make the small, and effective, change of using 'Returns(async ...' It has worked great! – rjacobsen0 Dec 23 '20 at 01:57