简体   繁体   中英

How to unit test when Microsoft.Azure.ServiceBus.Core.MessageReceiver is involved

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?

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

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 .

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

       ....
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM