So far I've been able to setup unit testing for Azure Functions and it works great. However for my current project I need to use dynamic or imperative bindings. https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#imperative-bindings
This leads to issues for my unit test I cannot seem to solve.
My function looks like this:
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");
}
}
}
Near the end of the code of the function, I configure this binder to hold a list of BrokeredMessages. This is done by calling the BindAsync on the binder.
The attributes are dynamically set and contain a servicebus connection and topic name. This all works great when deployed to Azure so functionality-wise everything is fine. So far so good.
However I'm stuggling with getting my test running. To be able to invoke the function, I need to provide parameters. The HttpTrigger this is pretty common, but for the Binder I don't know what to provide.
For testing I use this approach:
[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
}
I use a CustomBinder inherited from Binder, because just having an instance of Binder failed in the function on the 'BindAsync' throwing 'Object reference not set to an instance of an object'. It seems the constructor of the binder is actually not meant to be called.
In the CustomBinder I override the BindAsync to return a generic list of BrokeredMessages.
public class CustomBinder : Binder
{
public override async Task<TValue> BindAsync<TValue>(Attribute[] attributes, CancellationToken cancellationToken = new CancellationToken())
{
return (TValue)((object)(new List<BrokeredMessage>()));
}
}
Not entirely surprising that also failed throwing:
InvalidCastException: Unable to cast object of type 'System.Collections.Generic.List'1[Microsoft.ServiceBus.Messaging.BrokeredMessage]' to type 'Microsoft.Azure.WebJobs.IAsyncCollector`1[Microsoft.ServiceBus.Messaging.BrokeredMessage]'.
I cannot find an implementation of the IAsyncCollector, so maybe I need to approach this differently?
My actual goal is to be able to verify the list of brokered messages, as the function would output to Azure servicebus.
IBinder
interface, but it's more limited thanBinder
AFAIK. Does it have the method that you need? Why not mockIAsyncCollector
too? – Mikhail ShilkovIAsyncCollector
. VerifyingBrokeredMessage
might be challenging though, not even sure if you can read message content out of it. – Mikhail Shilkov