ASP.NET Core Unit Test: How to Mock HttpClient.GetStringAsync()
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()
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.
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
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK