本文转载自微信公众号「汪宇杰博客」,作者汪宇杰。转载本文请联系汪宇杰博客公众号。
在 ASP.NET Core 单元测试中模拟HttpClient.GetStringAsync() 的技巧。
问题
下面这个代码
- varhtml=await_httpClient.GetStringAsync(sourceUrl);
如果按正常思路像这样去 Mock HttpClient.GetStringAsync()
- varhttpClientMock=newMock<HttpClient>();
- httpClientMock
- .Setup(p=>p.GetStringAsync(It.IsAny<string>()))
- .Returns(Task.FromResult("…"));
Moq 框架就会爆
Exception
- System.NotSupportedException:Unsupportedexpression:p=>p.GetStringAsync(It.IsAny())Non-overridablemembers(here:HttpClient.GetStringAsync)maynotbeusedinsetup/verificationexpressions.
解决方法
我们需要 Mock HttpClient 底层使用的 HttpMessageHandler 而不是 HttpClient
- varhandlerMock=newMock<HttpMessageHandler>();
- varmagicHttpClient=newHttpClient(handlerMock.Object);
然后我花了 9.96 分钟研究了 HttpClient.GetStringAsync() 的源代码,发现它最终调用的是 SendAsync() 方法
- privateasyncTask<string>GetStringAsyncCore(HttpRequestMessagerequest,CancellationTokencancellationToken)
- {
- //…
- response=awaitbase.SendAsync(request,cts.Token).ConfigureAwait(false);
- //…
- }
源代码位置:https://source.dot.net/#System.Net.Http/System/Net/Http/HttpClient.cs,170
因此,我们的 Mock Setup 如下:
- handlerMock
- .Protected()
- .Setup<Task<HttpResponseMessage>>(
- "SendAsync",
- ItExpr.IsAny<HttpRequestMessage>(),
- ItExpr.IsAny<CancellationToken>()
- )
- .ReturnsAsync(newHttpResponseMessage
- {
- StatusCode=HttpStatusCode.OK,
- Content=newStringContent("thestringyouwanttoreturn")
- })
- .Verifiable();
现在 Mock 就能运行成功了!
最后附上完整的 UT 代码供参考:
- usingSystem.Net;
- usingSystem.Net.Http;
- usingSystem.Threading;
- usingSystem.Threading.Tasks;
- usingMicrosoft.Extensions.Logging;
- usingMoq;
- usingMoq.Protected;
- usingNUnit.Framework;
- namespaceMoonglade.Pingback.Tests
- {
- [TestFixture]
- publicclassPingSourceInspectorTests
- {
- privateMockRepository_mockRepository;
- privateMock<ILogger<PingSourceInspector>>_mockLogger;
- privateMock<HttpMessageHandler>_handlerMock;
- privateHttpClient_magicHttpClient;
- [SetUp]
- publicvoidSetUp()
- {
- _mockRepository=new(MockBehavior.Default);
- _mockLogger=_mockRepository.Create<ILogger<PingSourceInspector>>();
- _handlerMock=_mockRepository.Create<HttpMessageHandler>();
- }
- privatePingSourceInspectorCreatePingSourceInspector()
- {
- _magicHttpClient=new(_handlerMock.Object);
- returnnew(_mockLogger.Object,_magicHttpClient);
- }
- [Test]
- publicasyncTaskExamineSourceAsync_StateUnderTest_ExpectedBehavior()
- {
- stringsourceUrl="https://996.icu/work-996-sick-icu";
- stringtargetUrl="https://greenhat.today/programmers-special-gift";
- _handlerMock
- .Protected()
- .Setup<Task<HttpResponseMessage>>(
- "SendAsync",
- ItExpr.IsAny<HttpRequestMessage>(),
- ItExpr.IsAny<CancellationToken>()
- )
- .ReturnsAsync(newHttpResponseMessage
- {
- StatusCode=HttpStatusCode.OK,
- Content=newStringContent($"<html>"+
- $"<head>"+
- $"<title>Programmer'sGift</title>"+
- $"</head>"+
- $"<body>Work996andhavea<ahref=\\"{targetUrl}\\">greenhat</a>!</body>"+
- $"</html>")
- })
- .Verifiable();
- varpingSourceInspector=CreatePingSourceInspector();
- varresult=awaitpingSourceInspector.ExamineSourceAsync(sourceUrl,targetUrl);
- Assert.IsFalse(result.ContainsHtml);
- Assert.IsTrue(result.SourceHasLink);
- Assert.AreEqual("Programmer'sGift",result.Title);
- Assert.AreEqual(targetUrl,result.TargetUrl);
- Assert.AreEqual(sourceUrl,result.SourceUrl);
- }
- }
- }
原文链接:https://mp.weixin.qq.com/s/Ljst8xxnC0iURU4Ev6RSpQ