Azure函数在使用命令式绑定时进行测试

时间:2018-01-11 20:59:18

标签: c# unit-testing azure-functions

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

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

我的功能如下:

curl -x <myCompanyProxy>:<port> -k -O -L <link to file to download>

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

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

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

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

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");
        }
    }
}

我使用从Binder继承的CustomBinder,因为在BindAsync&#39;的函数中只有一个Binder实例失败了。 throw&#39;对象引用未设置为对象的实例&#39;。看来绑定器的构造函数实际上并不是要调用它。

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

[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
}

投掷失败也不足为奇:

  

InvalidCastException:无法转换类型&System; System.Collections.Generic.List&#39; 1 [Microsoft.ServiceBus.Messaging.BrokeredMessage]&#39;键入&#39; Microsoft.Azure.WebJobs.IAsyncCollector`1 [Microsoft.ServiceBus.Messaging.BrokeredMessage]&#39;。

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

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

1 个答案:

答案 0 :(得分:0)

正如评论中所提到的,我同意嘲笑它是有道理的。您明确希望单元测试您自己的代码逻辑。只考虑您自己的业务逻辑,您可以假设实际的远程操作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

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

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

希望这会有所帮助......