Azure Functions unit testing when using imperative binding

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.

This leads to issues for my unit test I cannot seem to solve.

The signature of my function looks like this:

public static async Task<HttpResponseMessage>
    Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] 
        HttpRequestMessage req, Binder binder)

Somewhere in the code of the function, I configure this binder to hold a list of BrokeredMessages.

var outputSbMessage = await binder.BindAsync<IAsyncCollector<BrokeredMessage>>(attributes);

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 the unit 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.

In my unit test I invoke the function using:

var myBinder = new CustomBinder();
var response = MyFunc.Run(httpRequest, mybinder);

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'.

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 appraoch this differently?

1 answer

  • answered 2018-01-11 20:59 Juliën

    As mentioned in the comments, I would agree that mocking it makes sense. You explicitely want to unit test your own code logic. With only your own business logic in mind, you may assume that the actual the actual remote operation binder.BindAsync(...) - of which you have no control over - works as expected.

    Mocking it in a unit test should work with something like this:

    using FluentAssertions;
    using Microsoft.Azure.WebJobs;
    using Microsoft.ServiceBus.Messaging;
    using Xunit;
    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))
        // act
        var target = await mock.Object.BindAsync<BrokeredMessage>(attribute);
        // assert

    I understand that your concern may be a full integration test. You seem to want to test the entire chain. In that case, having a unit test might prove difficult because you depend on an external system. If that is the case you might want to create a seperate integration test on top of it, by setting up a seperate instance.

    Considering your function is setup as a HttpTrigger, the following should work:

    # 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 

    Next, simply execute a http request to the hosted endpoint:

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

    In this case you have a clean and isolated integration setup.

    Either way, I'd like to argue to either go the route of the unit test with mock OR setting up a seperate integration test setup for it.

    Hope this helps...