0

I have the following code using an HttpClient. I'm new to C# and would like to learn how to unit test my HttpClient but am not sure where to begin. Here is my code:

 protected override async Task PopulateData()
        {
            using (var client = new HttpClient())
            {
                var token = "private token";
                var requestUrl = api_url_here;
                var authenticatedRequestUrl = requestUrl + $"{token}";
                var response = await client.GetAsync(authenticatedRequestUrl);
                var stringResult = await response.Content.ReadAsStringAsync();

                // do something
            }
        }

I've seen lots of different articles suggesting different ways to unit test but I am unsure as to how to use them properly. For example, I've seen this unit test pattern on many websites:

  [Test]
        public async Task MockingHTTP()
        {
            var requestUri = new Uri("");
            var expectedResponse = "Response";
            var mockHandler = new Mock<HttpMessageHandler>();

            mockHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
                ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage(Net.HttpStatusCode.OK));

            var httpClient = new HttpClient(mockHandler.Object);
            var result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
            Assert.AreEqual(expectedResponse, result);

            }
        }

However I don't know how to apply this approach to my code. Please can someone point my in the right direction in order to successfully unit test the HttpClient?

5
  • You don't know if it's the "correct" approach? Did it work? Is it easy to read? Did you understand it? What definition of correct are you looking for? There's no one right way to do things (though there's often a completely wrong way). In this case, I don't think you need to ask us whether it's right or not - you're equipped to judge that for yourself. Commented Jan 31, 2020 at 14:22
  • 1
    By the way - HttpClient is a bit of a special child. Even though it implements IDisposable, it's not a good idea to use it like it is. See You're using HttpClient wrong and it is destabilizing your software and You're (probably still) using HttpClient wrong and it is destabilizing your software. Instead, I recommend Flurl, which is easier to read and test and avoids those pitfalls. Commented Jan 31, 2020 at 14:23
  • @mason I have edited my question to explain properly - sorry, asking if it was the "correct approach" perhaps wasn't best. What I mean is that I don't know how to use it or apply it to my code but I also don't know where to start with mocking the HttpClient or unit testing it and was looking for some guidance in the right direction to get me started. Commented Jan 31, 2020 at 14:24
  • Testing a mocked instance is useless in any case. A mocked instance is used for building a test environment and not anything you will test itself Commented Jan 31, 2020 at 14:25
  • 3
    @SirRufo No, it's not useless. The point isn't to test HttpClient is working correctly - that's Microsoft's job. The point is to test PopulateData to make sure it does its work properly. That's just basic unit testing - mock the dependencies. Commented Jan 31, 2020 at 14:27

1 Answer 1

2

The first thing to do when unit testing is to identify your "System Under Test" or SUT. That's the thing that you need to verify the behavior of. Any dependencies of your SUT should be mocked, you shouldn't use the real version.

In this case, you're using HttpClient, but you've got no way to use a different handler in your method. Using a different handler is the easiest way to fake responses for HttpClient. HttpClient accepts a different handler via the constructor. So you'd need to adjust your code like this:

public class YourClassName
{
    private readonly HttpMessageHandler _httpMessageHandler;

    public YourClassName(HttpMessageHandler httpMessageHandler)
    {
        _httpMessageHandler = httpMessageHandler;
    }

    protected override async Task PopulateData()
    {
        using (var client = new HttpClient(_httpMessageHandler))
        {
            var token = "private token";
            var requestUrl = api_url_here;
            var authenticatedRequestUrl = requestUrl + $"{token}";
            var response = await client.GetAsync(authenticatedRequestUrl);
            var stringResult = await response.Content.ReadAsStringAsync();

            // do something
        }
    }
}

Now it's unit testable, because you can mock the HttpMessageHandler. But the code to do this is cumbersome. And considering the other flaws you've got in using HttpClient, it's probably best to not even use HttpClient in the first place. See:

What my team does is use Flurl instead. It has a nicer syntax, doesn't have the weird wonkiness with IDisposable that HttpClient has, and is easy to unit test.

protected override async Task PopulateData()
{
    var token = "private token";
    var requestUrl = api_url_here;
    var authenticatedRequestUrl = requestUrl + $"{token}";
    var stringResult = authenticatedRequestUrl.
    var response = await authenticatedRequestUrl.GetAsync();
    var stringResult = await response.Content.ReadAsStringAsync();

    // do something
}

Then your unit test becomes a lot simpler:

[Test]
public async Task PopulateDataTest()
{
    var requestUri = new Uri("SomeUriWithAToken");
    var expectedResponse = "some response body";
    var systemUnderTest = new YourClassName();

    using (var httpTest = new HttpTest())
    {
        httpTest.RespondWith(expectedResponse);
        var result = await systemUnderTest.PopulateData();
        Assert.AreEqual(expectedResponse, result);
        httpTest.ShouldHaveCalled(requestUri);
    }
}

Flurl's documentation is great. You can have it return all sorts of responses, or even automatically deserialize a response to a C# class.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.