使用命令式绑定时的 Azure Functions 测试

2024-04-02

到目前为止,我已经能够为 Azure Functions 设置单元测试,并且效果很好。然而,对于我当前的项目,我需要使用动态或命令式绑定。https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#imperative-bindings https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#imperative-bindings

这导致我的单元测试出现问题,我似乎无法解决。

我的函数如下所示:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.ServiceBus.Messaging;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace My.Functions
{
    public static class MyFunc
    {
        [FunctionName("my-func")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestMessage req,
            Binder binder)
        {
            dynamic data = await req.Content.ReadAsAsync<object>();
            byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
            MemoryStream stream = new MemoryStream(bytes, writable: false);

            var sbMsg = new BrokeredMessage(stream) { ContentType = "application/json" };
            var attributes = new Attribute[]
            {
                new ServiceBusAccountAttribute("some-sb-account"),
                new ServiceBusAttribute("some-queue-or-topic", AccessRights.Send)
            };
            var outputSbMessage = await binder.BindAsync<IAsyncCollector<BrokeredMessage>>(attributes);
            await outputSbMessage.AddAsync(sbMsg);

            return req.CreateResponse(HttpStatusCode.OK, "OK");
        }
    }
}

在函数代码的末尾附近,我配置此绑定器来保存 BrokeredMessages 列表。这是通过调用活页夹上的 BindAsync 来完成的。

这些属性是动态设置的,包含服务总线连接和主题名称。当部署到 Azure 时,这一切都非常有效,因此功能方面一切都很好。 到目前为止,一切都很好。

然而,我正在努力让我的测试运行。为了能够调用该函数,我需要提供参数。 HttpTrigger 这很常见,但对于 Binder 我不知道要提供什么。

为了进行测试,我使用这种方法:

[TestMethod]
public void SendHttpReq()
{
    // Setup
    var httpRequest = GetHttpRequestFromTestFile("HttpRequest");
    var sbOutput = new CustomBinder();

    // Act
    var response = SendToServicebus.Run(httpRequest, sbOutput);

    // Assert
    Assert.AreEqual(sbOutput.Count(), 1);

    // Clean up
}

我使用从 Binder 继承的 CustomBinder,因为只有 Binder 实例在“BindAsync”函数中失败,抛出“对象引用未设置到对象实例”。看来绑定器的构造函数实际上并不应该被调用。

在 CustomBinder 中,我重写 BindAsync 以返回 BrokeredMessages 的通用列表。

public class CustomBinder : Binder
{
 public override async Task<TValue> BindAsync<TValue>(Attribute[] attributes, CancellationToken cancellationToken = new CancellationToken())
 {
  return (TValue)((object)(new List<BrokeredMessage>()));
  }
}

投掷也失败并不完全令人惊讶:

InvalidCastException:无法将类型“System.Collections.Generic.List”1[Microsoft.ServiceBus.Messaging.BrokeredMessage]”的对象转换为类型“Microsoft.Azure.WebJobs.IAsyncCollector`1[Microsoft.ServiceBus.Messaging.BrokeredMessage]” 。

我找不到 IAsyncCollector 的实现,所以也许我需要以不同的方式处理这个问题?

我的实际目标是能够验证代理消息列表,因为该函数将输出到 Azure 服务总线。


正如评论中提到的,我同意嘲笑它是有道理的。你明确想要单元测试你自己的代码逻辑。仅考虑您自己的业务逻辑,您可能会假设实际的实际远程操作binder.BindAsync(...)- 您无法控制 - 按预期工作。

在单元测试中模拟它应该像这样:

using FluentAssertions;
using Microsoft.Azure.WebJobs;
using Microsoft.ServiceBus.Messaging;
using Xunit;

[Fact]
public async Task AzureBindAsyncShouldRetrunBrokeredMessage()
{
    // arrange           
    var attribute = new ServiceBusAccountAttribute("foo");
    var mockedResult = new BrokeredMessage()
    {
        Label = "whatever"
    };

    var mock = new Mock<IBinder>();
    mock.Setup(x => x.BindAsync<BrokeredMessage>(attribute, CancellationToken.None))
        .ReturnsAsync(mockedResult);

    // act
    var target = await mock.Object.BindAsync<BrokeredMessage>(attribute);

    // assert
    target.Should().NotBeNull();
    target.Label.Should().Be("whatever");
}

我理解您担心的可能是完整的集成测试。您似乎想测试整个链条。在这种情况下,进行单元测试可能会很困难,因为您依赖于外部系统。如果是这种情况,您可能想要创建一个单独的集成测试在其之上,通过设置一个单独的实例。

考虑到您的功能设置为HttpTrigger,以下应该有效:

# using azure functions cli (2.x), browse to the output file
cd MyAzureFunction/bin/Debug/netstandard2.0

# run a new host/instance if your function
func host start 

接下来,只需向托管端点执行 http 请求:

$ [POST] http://localhost:7071/api/HttpTriggerCSharp?name=my-func

在这种情况下,您有一个干净且隔离的集成设置。

无论哪种方式,我都想争论要么使用模拟进行单元测试,要么为其设置单独的集成测试设置。

希望这可以帮助...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用命令式绑定时的 Azure Functions 测试 的相关文章

随机推荐