16

ASP.NET Core Unit Test: How to Mock HttpClient.GetStringAsync()

 2 years ago
source link: https://edi.wang/post/2021/5/11/aspnet-core-unit-test-how-to-mock-httpclientgetstringasync
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ASP.NET Core Unit Test: How to Mock HttpClient.GetStringAsync()

Tuesday, May 11, 2021 China Standard Time 1 Reads

In ASP.NET Core unit tests, if you want to mock HttpClient.GetStringAsync(), here's the trick.

Problem


Given the following code

var html = await _httpClient.GetStringAsync(sourceUrl);

When I tried to mock HttpClient.GetStringAsync() like this

var httpClientMock = new Mock<HttpClient>();
httpClientMock
    .Setup(p => p.GetStringAsync(It.IsAny<string>()))
    .Returns(Task.FromResult("..."));

Moq will blow up sky high

System.NotSupportedException : Unsupported expression: p => p.GetStringAsync(It.IsAny<string>())
Non-overridable members (here: HttpClient.GetStringAsync) may not be used in setup / verification expressions.

img-146d0902-0fd8-442e-ae81-e34019357f2e.png

Solution


Instead of mocking HttpClient type, we need to mock the underlying HttpMessageHandler that HttpClient uses.

var handlerMock = new Mock<HttpMessageHandler>();
var magicHttpClient = new HttpClient(handlerMock.Object);

Then I took some time looking into the source code behind HttpClient.GetStringAsync() and found it is using SendAsync() method behind the scene.

private async Task<string> GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
{
    // ...
    response = await base.SendAsync(request, cts.Token).ConfigureAwait(false);
    // ...
}

So we need to setup the mock like this.

handlerMock
    .Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>()
    )
    .ReturnsAsync(new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent("the string you want to return")
    })
    .Verifiable();

Now, my mock can be run successfully!

Finally, the complete unit test code for reference:

using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Moq;
using Moq.Protected;
using NUnit.Framework;

namespace Moonglade.Pingback.Tests
{
    [TestFixture]
    public class PingSourceInspectorTests
    {
        private MockRepository _mockRepository;

        private Mock<ILogger<PingSourceInspector>> _mockLogger;
        private Mock<HttpMessageHandler> _handlerMock;
        private HttpClient _magicHttpClient;

        [SetUp]
        public void SetUp()
        {
            _mockRepository = new(MockBehavior.Default);
            _mockLogger = _mockRepository.Create<ILogger<PingSourceInspector>>();
            _handlerMock = _mockRepository.Create<HttpMessageHandler>();
        }

        private PingSourceInspector CreatePingSourceInspector()
        {
            _magicHttpClient = new(_handlerMock.Object);
            return new(_mockLogger.Object, _magicHttpClient);
        }

        [Test]
        public async Task ExamineSourceAsync_StateUnderTest_ExpectedBehavior()
        {
            string sourceUrl = "https://996.icu/work-996-sick-icu";
            string targetUrl = "https://greenhat.today/programmers-special-gift";

            _handlerMock
                .Protected()
                .Setup<Task<HttpResponseMessage>>(
                    "SendAsync",
                    ItExpr.IsAny<HttpRequestMessage>(),
                    ItExpr.IsAny<CancellationToken>()
                )
                .ReturnsAsync(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent($"<html>" +
                                                $"<head>" +
                                                $"<title>Programmer's Gift</title>" +
                                                $"</head>" +
                                                $"<body>Work 996 and have a <a href=\"{targetUrl}\">green hat</a>!</body>" +
                                                $"</html>")
                })
                .Verifiable();
            var pingSourceInspector = CreatePingSourceInspector();

            var result = await pingSourceInspector.ExamineSourceAsync(sourceUrl, targetUrl);
            Assert.IsFalse(result.ContainsHtml);
            Assert.IsTrue(result.SourceHasLink);
            Assert.AreEqual("Programmer's Gift", result.Title);
            Assert.AreEqual(targetUrl, result.TargetUrl);
            Assert.AreEqual(sourceUrl, result.SourceUrl);
        }
    }
}

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK