6

We've mocked the HttpMessageHandler, so we can test a class that uses the HttpClient.

One of our methods under test creates a new HttpClient, calls PostAsync, and disposes the HttpClient. We would like to test the Content of the HTTP request like this:

Assert.Equal("", ActualHttpRequestMessage.Content.ReadAsStringAsync());

The problem is that we "Cannot access a disposed object," because the HttpClient disposes the Content.

Question How can we inspect the content?

This is our Moq setup.

MockHttpMessageHandler = new Mock<HttpMessageHandler>();

MockHttpMessageHandler
    .Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>())
    .Callback<HttpRequestMessage, CancellationToken>(
        (httpRequestMessage, cancellationToken) =>
        {
            ActualHttpRequestMessage = httpRequestMessage;
        })
    .Returns(
        Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(string.Empty)
        }));

This is how we are using it in the class under test.

new HttpClient(HttpMessageHandler);
Community
  • 1
  • 1
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • I won't add this as an answer since it's my own library, but have you looked at [MockHttp](https://github.com/richardszalay/mockhttp)? It provides a nicer DSL for mocking HttpClient requests compared to a dynamic mocking library like Moq. – Richard Szalay Jan 19 '17 at 03:30

1 Answers1

8

Change your mock to this:

MockHttpMessageHandler
    .Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>())
    .Callback<HttpRequestMessage, CancellationToken>(
        (httpRequestMessage, cancellationToken) =>
        {
            // +++++++++++++++++++++++++++++++++++++++++
            // Read the Content here before it's disposed
            ActualHttpRequestContent = httpRequestMessage.Content
                .ReadAsStringAsync()
                .GetAwaiter()
                .GetResult();

            ActualHttpRequestMessage = httpRequestMessage;
        })
    .Returns(
        Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(string.Empty)
        }));

Then you can test like this:

Assert.Equal("", ActualHttpRequestContent);

Keep in mind that we can only read Content once, so if you try to read it later, it will be empty. It's like a Heisenberg object.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
dkackman
  • 15,179
  • 13
  • 69
  • 123
  • It is not our test method that is disposing the content. It is our method under test that is doing that. Our method under test is calling `HttpClient.PostAsync`, which disposes the `HttpResponseMessage.Content` automatically. – Shaun Luttin Jan 19 '17 at 02:24
  • My apologies. I thought you were referring to the response message. Why does your unit test need to inspect the request content AFTER the post? Inspect it prior. Posting it isn't going to change the request content. (or - your unit test is inspecting the request but your comment refers to the response) – dkackman Jan 19 '17 at 02:31
  • How can we inspect it prior to posting? – Shaun Luttin Jan 19 '17 at 02:34
  • Your assert is testing ActualHttpRequestMessage.Content. If you are truly trying to unit test the request, you are the one setting up the content. Are you sure that shouldn't be ActualHttpResponseMessage.Content? – dkackman Jan 19 '17 at 02:35
  • Yes. We want to test the request that we're sending. We do not care about the response. Basically, the method under test takes a bunch of parameters, turns them into an `HttpClient` request, and then calls `PostAsync`. We want to test that the post content is what it ought to be. – Shaun Luttin Jan 19 '17 at 02:36
  • And you need to test the request content after Posting? Isn't there some chunk of code the generates the content object that can be tested in isolation? – dkackman Jan 19 '17 at 02:40
  • The method under test calls `private` methods that generate the content object. I'm hesitant to make that method `public`. because it will make refactoring more difficult. – Shaun Luttin Jan 19 '17 at 02:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/133505/discussion-between-dkackman-and-shaun-luttin). – dkackman Jan 19 '17 at 02:50