如何在 VB.NET 中模拟 HttpResponseMessage SendAsync?

How can I Mock HttpResponseMessage SendAsync in VB.NET?

提问人:CyberTooth Tiger 提问时间:10/13/2023 最后编辑:CyberTooth Tiger 更新时间:11/6/2023 访问量:66

问:

我找到了一百篇关于如何在 C# 中执行此操作的帖子,但没有一篇关于如何在 VB.Net 中执行此操作的帖子。每一次将这些方法转化为 VB.NET 的尝试都失败了。这似乎归结为 SendAsync 是“受保护的朋友”这一事实,如以下错误图像所示: <在马克·西曼(Mark Seeman)的指导下被移除 - 见下文>

如果您看不到图像,则我收到的错误是:“HttpMessageHandler.Protected Friend MustOverloads Function SendAsync(request As HttpRequestMessage, candellationToken As CancellationToken) As Task(Of HttpResponseMessage)”在此上下文中无法访问,因为它是“受保护的朋友”。错误号为 BC30390,这会导致非常有用的Microsoft错误页面“抱歉,我们没有关于此 Visual Basic 错误的详细信息”。

首先,这是一个跨越多个部门的应用程序的庞然大物,更改为 C# 不是一种选择。我正在对业务逻辑的一部分进行单元测试,该逻辑从另一个系统调用 API,然后在返回该数据后执行其他代码并返回结果。测试数据不能指望从外部 API 返回的结果,因此我需要模拟 API 结果以与我的测试数据匹配。这是我尝试测试的 BL 函数的那部分:

    Public Sub New(context As IUSASContext, account As IAccount, configurationManager As IDBConfigurationManager)
        _Context = context
        _Account = account
        _AppSettingsConfigManager = configurationManager
        _UserPreferenceHistory = New UserPreferenceHistory(_Context, _Account)
    End Sub


    Public Async Function GetUserPreferenceResponseData(id As Long, httpClient As HttpClient) As Task(Of UserPreferences)
        Dim baseUri = New Uri(AppSettingsHelper.GetValue(AppSettingName.ActivitySummaryApiUri, "", _AppSettingsConfigManager))
        Dim userContentReponse = Await httpClient.GetAsync(New Uri(baseUri, "/api/v1/UserContents"))
        Dim userContentData = Await userContentReponse.Content.ReadAsStringAsync()

        Dim userResponse = Await httpClient.GetAsync(New Uri(baseUri, $"/api/v1/Users/{_Account.CurrentTenant}/{id}"))
        Dim userData = Await userResponse.Content.ReadAsStringAsync()

        Dim userPreferences As UserPreferences = Newtonsoft.Json.JsonConvert.DeserializeObject(Of UserPreferences)(userData)
        userPreferences.SchedulesList = GetUserPreferencesEmailScheduleList()
        userPreferences.UserContentsList = Newtonsoft.Json.JsonConvert.DeserializeObject(Of List(Of Opm.Staffing.Models.ActivitySummary.UserContent))(userContentData)
        userPreferences.Id = id

        Return userPreferences
    End Function

首先,我尝试模拟 HttpClient 本身,并设置“GetAsync”函数,但事实证明这是不可能的。快速的 Web 搜索证明,普遍的共识是模拟 HttpResponseMessage 并设置“SendAsync”。但是,C# 示例显示将“Protected”放在“Setup”关键字之前,如以下示例所示:

mockHttpMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent("{'name':thecodebuzz,'city':'USA'}"),
                });

当我尝试这样做时,我收到以下错误: <在马克·西曼(Mark Seeman)的指导下被移除 - 见下文> 如果您看不到错误,则文本为:“Lambda 表达式无法转换为'String',因为'String'不是委托类型。

这是我的单元测试的最新版本:

        <TestMethod()> Public Sub GetUserPreferenceResponseData()

        'Arrange
        Dim methodName = System.Reflection.MethodBase.GetCurrentMethod().Name

        Dim asuc As New ActivitySummary.UserContent() With {.UserContentId = 1, .UserContentName = "BB"}
        Dim uc As New List(Of ActivitySummary.UserContent) From {asuc}

 ' the below produces the "Protected Friend" intellesense error
 '_HTTPMessageHandlerMock.Setup(Function(x) x.SendAsync(It.IsAny(Of HttpRequestMessage), It.IsAny(Of Threading.CancellationToken))) _
 .Returns(New HttpResponseMessage With {.Content = New StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(uc))})

 ' the below produces the "String not a Delegate Type " intellesense error
 _HTTPMessageHandlerMock.Protected().Setup(Function(x) x.SendAsync(It.IsAny(Of HttpRequestMessage), It.IsAny(Of Threading.CancellationToken))) _
 .Returns(New HttpResponseMessage With {.Content = New StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(uc))})

        Dim _Client = New HttpClient(_HTTPMessageHandlerMock.Object)

        Dim userPreferencesBL As New UserPreferencesBL(_Context, _Account, _dbConfigManager)
        Dim uid As Integer

        'Action
        Dim result = userPreferencesBL.GetUserPreferenceResponseData(uid, _Client)

        'Assert
        Assert.IsNotNull(result, methodName + " returned null, expected User Preferences.")

    End Sub

将鼠标悬停在“SendAsync”上会显示我上面提到的错误。

我一直在兜圈子,因为改变一件事会破坏另一件事,我以为我已经解决了。任何帮助将不胜感激。

vb.net 最小起订 量保护 httpresponsemessage sendasync

评论

0赞 Michael Foster 10/14/2023
也许它在这里的名声不好,但这听起来像是 ChatGPT 的一个很好的用途:将一种语言翻译成另一种语言。如果它不起作用,那么它不会花费很多时间。
0赞 Mark Seemann 10/14/2023
请不要上传代码/数据/错误的图片。
0赞 Craig 10/14/2023
@MichaelFoster我猜它不会有太大帮助,因为 C# 看起来并不复杂。目前还不清楚为什么等效的 VB 不起作用。
0赞 Mark Seemann 10/14/2023
“当我尝试这样做时......”当你尝试做什么时?请编辑帖子以包含最小可重复的示例。一旦到位,请随时联系我,我会再看一遍。
0赞 CyberTooth Tiger 10/16/2023
@MarkSeemann 谢谢!!我添加了单元测试的简化版本以及 BL 类的构造函数。这足以让我知道我需要什么吗?或者我是否还需要添加模型、上下文和虚假定义?

答:

0赞 Mark Seemann 10/16/2023 #1

这样的事情应该可以工作:

Dim httpMessageHandlerMock As New Mock(Of HttpMessageHandler)
httpMessageHandlerMock _
    .Protected() _ ' Needs Imports Moq.Protected
    .Setup(Of Task(Of HttpResponseMessage))(
        "SendAsync",
        ItExpr.IsAny(Of HttpRequestMessage),
        ItExpr.IsAny(Of Threading.CancellationToken)) _
    .ReturnsAsync(
        New HttpResponseMessage() With
        {
            .Content = New JsonConvert.SerializeObject(uc))
        })

Dim client = New HttpClient(httpMessageHandlerMock.Object)

请注意,正如评论所说,您需要

Imports Moq.Protected

否则,扩展方法和该 API 的其余部分将不可用。Protected

您不能将 lambda 表达式或其他强类型表达式与访问权限一起使用,因为该方法仅对继承类可见,而 test 或 不继承自 。因此,您需要用字符串 () 标识要覆盖的方法,并使用 API 匹配输入参数。ProtectedhttpMessageHandlerMockHttpMessageHandler"SendAsync"ItExpr

0赞 Perringaiden 11/6/2023 #2

作为使用 HttpClient VB.NET 进行类似测试的人,我已经实现了自己的测试,它覆盖了 以“处理”请求并生成受控内容。DelegatingHandlerSendAsync

我将发布我正在做的事情的简化版本,但关键是,根据要测试的方法路径,使用一组处理程序构建。TestDelegatingHandler

Private ReadOnly cgHandlers As Dictionary(Of String, Func(Of HttpRequestMessage, HttpResponseMessage))

''' <summary>
''' Creates a new <see cref="TestDelegatingHandler"/> with custom handlers.
''' </summary>
''' <param name="handlers">
''' The handlers as a Tuple of LocalPath As <see cref="String"/> and Handler
''' As <see cref="Func(Of HttpRequestMesage, HttpResponseMessage)"/>.
''' </param>
Public Sub New(
    ParamArray handlers() As (LocalPath As String, Handler As Func(Of HttpRequestMessage, HttpResponseMessage))
)

    cgHandlers = handlers.ToDictionary(Function(x) x.LocalPath, Function(x) x.Handler)

End Sub

''' <inheritdoc />
Protected Overrides Async Function SendAsync(
        request As HttpRequestMessage,
        cancellationToken As Threading.CancellationToken
    ) As Task(Of HttpResponseMessage)

    Dim rc As HttpResponseMessage
    Dim message As String
    Dim handler As Func(Of HttpRequestMessage, HttpResponseMessage) = Nothing
    Dim localPath As String


    localPath = request.RequestUri.LocalPath

    If cgHandlers.TryGetValue(localPath, handler) Then
        rc = handler.Invoke(request, message)
    Else
        rc = New HttpResponseMessage(Net.HttpStatusCode.NotFound) With {
            .RequestMessage = request
        }
    End If

    Return Await Task.FromResult(rc)
End Function

然后,Used 接受处理程序以创建客户端。IHttpClientFactory

Private Shared Function GetHttpClientFactory(delegatingHandler As DelegatingHandler) As IHttpClientFactory
    Dim mock As Mock(Of IHttpClientFactory)


    mock = New Mock(Of IHttpClientFactory)
    mock.Setup(Function(x) x.CreateClient(It.IsAny(Of String)())).Returns(Function() New HttpClient(delegatingHandler))

    Return mock.Object
End Function

或者(基于您的代码),只需向客户端提供:

New HttpClient(New TestDelegatingHandler(...handlers...))

它不会通过样式处理验证,但您绝对可以控制响应消息。Mock(Of )

评论

0赞 Perringaiden 11/6/2023
我意识到这并不能解释为什么使用 DelegatingHandler 而不是 HttpMessageHandler 作为祖先。我很确定您可以直接从 HttpMessageHandler 继承,但是我们的测试需要 DelegatingHandler,因为这是我们真实代码的输入。