3

I'm trying to unit test an Azure Function which has a dependency with MessageReceiver class. The class definition looks like:

public class MessageReceiver : ClientEntity, IMessageReceiver, IReceiverClient, IClientEntity

And the methods that I need to moq belong to the interface IMessageReceiver

I tried Mocking the class MessageReceiver, and Setup the method CompleteAsync but I got the error:

Non-overridable members (here: MessageReceiver.CompleteAsync) may not be used in setup / verification expressions.

Which afaik means that methods that are not virtual, abstract, or override can not be overriden/moq. I tried manually creating a child class and declaring a method CompleteAsync with the new keywork in the definition,

public new Task CompleteAsync(string lockToken)

in this case the code throws an error at the point where the CompleteAsync method is called:

Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).

But I don't think it can work, since I tried pressing F11 to enter the method, and my method was not called... Any ideas or sugestions?

J.J
  • 881
  • 16
  • 29
  • 3
    You generally want to inject interfaces, not implementations. Is there a reason you're injecting `MessageReceiver` instead of `IMessageReceiver`? – devNull Nov 19 '20 at 03:28
  • 2
    Please share your Function code snippet (at least the minimal relevant part). It's often about how you write your code to be unit testable than any particular mocking framework. – krishg Nov 19 '20 at 05:25
  • @devNull The reason is that I inherited the project like that, unfortunately I'm not expert on Azure Functions so I don't dare at this point to remove the more complex class MessageReceiver for just some of it's interfaces...thought it may be the right way. – J.J Nov 19 '20 at 15:33
  • @krishg That is a good point. In this case I have several lines of bussines logic and at the end a call to messageReceiver.CompleteAsync, which will throw an exception since I'm using a null instance(..because I can't create a moq) – J.J Nov 19 '20 at 15:38
  • 2
    Unfortunately it appears that you can't currently inject the `IMessageReceiver` interface anyways. There's an open feature request in GitHub to support that behavior: https://github.com/Azure/azure-webjobs-sdk/issues/2365 – devNull Nov 19 '20 at 15:39
  • I just upvoted the request – J.J Nov 19 '20 at 15:41
  • So not useful to leave a negative vote without leaving an explanation... – J.J Nov 19 '20 at 22:45

3 Answers3

9

My solution is:

  1. Make a public property on the Azure function class. (MessageReceiver)

  2. Set the value of this property in the Run method when the reference is null

  3. Set the value of this property in the unit test with a mock before invoking the Run method.

     //Azure Function:
     public class ServiceReportingFunction
     {
         private ILogger<ServiceReportingFunction> Logger { get; }
    
         //Needed for unit testing until 'IMessageReceiver messageReceiver' is supported as argument in the Run Method.
         public IMessageReceiver MessageReceiver { get; set; } // Step 1
    
    
         public ServiceReportingFunction(IConfigurationSettings configurationSettings, IReportSubscriptionClient reportSubscriptionClient, IServiceTokenProviderGateway serviceTokenProviderGateway, ILogger<ServiceReportingFunction> logger)
         {
             Logger = logger;
         }
    
         [FunctionName("ServiceReportingFunction")]
         public async Task Run([ServiceBusTrigger("%ServiceReportingQueueName%", Connection = "ServiceBusConnectionString")]Message message, MessageReceiver messageReceiver)
         {
             // Step 2
             if (MessageReceiver == null)
                 MessageReceiver = messageReceiver;
    
             ...
         }
     }
    
     //Unit (Xunit) test:
     public class ServiceReportingFunctionTests
     {
         [Fact]
         public async void Test_ServiceReportingFunction()
         {
             var logger = A.Fake<ILogger<ServiceReportingFunction>>();
             var messageReceiver = A.Fake<IMessageReceiver>(); // Step 3
    
             var function = new ServiceReportingFunction(logger);
             function.MessageReceiver = messageReceiver;
    
             ....
    
             await function.Run(message, null);
    
            ....
         }
     }
    
StannieV
  • 106
  • 3
  • I like this approach, since I don't need to change the main signature of my function – J.J Nov 19 '20 at 22:43
5

As you found, Moq requires members to be overridable for setup, either through an interface or the virtual modifier (see this answer for more). So ideally in this case you would inject the IMessageReceiver interface and be able to mock it normally. However, it appears that you can't currently inject the IMessageReceiver interface in Azure Functions (see GitHub feature request).


As a workaround, you can create a wrapper for your function logic that accepts IMessageReceiver. That wrapper can be as simple as an "internal" (internal as in not decorated as an Azure Function trigger, not necessarily with the internal access modifier) method in the function class. For example, if your current method looks like:

[FunctionName("Foo")]
public Task RunAsync(
    [ServiceBusTrigger] Message serviceBusMessage,
    MessageReceiver messageReceiver)
{
    // implementation
}

You could add a separate, testable method that RunAsync would pass-through to:

[FunctionName("Foo")]
public Task RunAsync(
    [ServiceBusTrigger] Message serviceBusMessage,
    MessageReceiver messageReceiver)
{
    return TestableRunAsync(serviceBusMessage, messageReceiver);
}

public Task TestableRunAsync(
    [ServiceBusTrigger] Message serviceBusMessage,
    IMessageReceiver messageReceiver)
{
    // implementation
}

And then in your unit tests you can call TestableRunAsync with a Mock instance of IMessageReceiver.

devNull
  • 3,849
  • 1
  • 16
  • 16
  • It definitely works on the unit test side. But not sure if when casting the main class to one of it's interfaces I will be losing some functionality(logs info, or some other functionality...) – J.J Nov 19 '20 at 16:49
  • 1
    This is the approach I'm using as well. Works great. – Marc Nov 20 '20 at 19:18
  • You could make TestableRunAsync internal and a friend for the [assembly: InternalsVisibleTo("test.dll")]; – Wouter Apr 20 '21 at 09:04
0

My solution (Also see: https://github.com/Azure/azure-webjobs-sdk/pull/2218):

//Azure Function:
public class ServiceReportingFunction
{
    private ILogger<ServiceReportingFunction> Logger { get; }

    //Needed for unit testing until 'IMessageReceiver messageReceiver' is supported as argument in the Run Method.
    public IMessageReceiver MessageReceiver { get; set; }


    public ServiceReportingFunction(ILogger<ServiceReportingFunction> logger)
    {
        Logger = logger;
    }

    [FunctionName("ServiceReportingFunction")]
    public async Task Run([ServiceBusTrigger("%ServiceReportingQueueName%", Connection = "ServiceBusConnectionString")]Message message, MessageReceiver messageReceiver)
    {
        if (MessageReceiver == null)
            MessageReceiver = messageReceiver;

        ...
    }
}

//Unit (Xunit) test:
public class ServiceReportingFunctionTests
{
    [Fact]
    public async void Test_ServiceReportingFunction()
    {
        var logger = A.Fake<ILogger<ServiceReportingFunction>>();
        var messageReceiver = A.Fake<IMessageReceiver>();

        var function = new ServiceReportingFunction(logger);
        function.MessageReceiver = messageReceiver;
        
        ....

        await function.Run(message, null);

       ....
    }
}
StannieV
  • 106
  • 3