Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
287 views
in Technique[技术] by (71.8m points)

unit testing - Writing Mocks for delegates in an Azure Function using Moq

I have an Azure function which basically gets invoked upon HttpRequest to the endpoint. This function then makes calls to relevant sections in the database based on CREATE or UPDATE message that is passed in the payload.

public class InboundEvent
{
    private readonly Func<MessageType, IMessageProcessor> _serviceProvider;
    private readonly IAccessTokenValidator _accessTokenValidator;

    public InboundEvent(Func<MessageType, IMessageProcessor> serviceProvider, IAccessTokenValidator accessTokenValidator)
    {
        _serviceProvider = serviceProvider;
        _accessTokenValidator = accessTokenValidator;
    }

    [FunctionName("InboundEvent")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "abc/input")] HttpRequest req,
        ILogger log)
    {
        try
        {
            await _accessTokenValidator.ValidateToken(req);
            log?.LogInformation($"InboundMessageProcessor executed at: {DateTime.UtcNow}");
            
            var request = await ProcessRequest(req);
            await _serviceProvider(request.MessageType).ProcessMessage(request);
            
            log?.LogInformation("InboundMessageProcessor function executed successfully.");
            return new OkObjectResult("OK");
        }
        catch (Exception ex)
        {
            log?.Log(LogLevel.Error, ex, "Error");
            return new InternalServerErrorResult();
        }
    }

    private async Task<InputModel> ProcessRequest(HttpRequest req)
    {
        InputModel messageReq = new InputModel();
        if (req.Headers != null && req.Headers.Any())
        {
            // form the InputModel datamodel object 
            //  basically it can contain CREATE or UPDATE information
        }
        messageReq.Message = await new StreamReader(req.Body).ReadToEndAsync();
        return messageReq;           
    }
}

Where the ServiceProvider component invokes MainBookingProcessor that takes appropriate action based on the Transaction type "CREATE" or "UPDATE"

public class MainBookingProcessor : IMessageProcessor
{
    private readonly ICommandDispatcher _commandDispatcher;

    public MainBookingProcessor(ICommandDispatcher commandDispatcher)
    {
        _commandDispatcher = commandDispatcher ?? throw new ArgumentNullException(nameof(commandDispatcher));
    }

    public async Task ProcessMessage(InputModel req)
    {

        switch (req.TransactionType)
        {
            case TransactionType.CREATE:
                var command = new CreateBookingCommand()
                {
                    System = req.System,
                    MessageId = req.MessageId,
                    Message = req.Message
                };
                await _commandDispatcher.SendAsync(command);
                break;
            case TransactionType.UPDATE:
                var updateCommand = new UpdateBookingCommand()
                {
                    System = req.System,
                    MessageId = req.MessageId,
                    Message = req.Message
                };
                await _commandDispatcher.SendAsync(updateCommand);
                break;
            default:
                throw new KeyNotFoundException();
        }
    }
}

Now comes the main part of the issue that I'm facing. I'm writing a test component to test this Azure function using xUnit and Moq. For this I created an InboundEventTests class which will contain the test methods to test the Run method of the InboundEvent Azure function

public class InboundEventTests : FunctionTest
 {
        private InboundEvent _sut;
        private readonly Mock<IMessageProcessor> messageProcessorMock 
                        = new Mock<IMessageProcessor>();

        private readonly Mock<Func<MessageType, IMessageProcessor>> _serviceProviderMock
                        = new Mock<Func<MessageType, IMessageProcessor>>();

        private readonly Mock<IAccessTokenValidator> _accessTokenValidator
                        = new Mock<IAccessTokenValidator>();
        private readonly Mock<ILogger> _loggerMock = new Mock<ILogger>();
        private HttpContext httpContextMock;
        private HeaderDictionary _headers;
        private Mock<InputModel> inputModelMock = new Mock<InputModel>();

        
        public InboundEventTests()
        {
            inputModelMock.SetupProperty(x => x.Message, It.IsAny<string>());
            inputModelMock.SetupProperty(x => x.MessageId, It.IsAny<Guid>());
            inputModelMock.SetupProperty(x => x.System, It.IsAny<string>());
            
        }

        public HttpRequest HttpRequestSetup(Dictionary<String, StringValues> query, string body)
        {
            var reqMock = new Mock<HttpRequest>();
            reqMock.Setup(req => req.Headers).Returns(new HeaderDictionary(query));
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(body);
            writer.Flush();
            stream.Position = 0;
            reqMock.Setup(req => req.Body).Returns(stream);
            return reqMock.Object;
        }
        
        private HeaderDictionary CreateHeaders()
        {
            _headers = new HeaderDictionary();

            _headers.TryAdd("MessageType","BOOKING");
            _headers.TryAdd("TransactionType", "UPDATE");
            _headers.TryAdd("MessageId", "some guid");
            _headers.TryAdd("System", "NSCP_ORDER_MANAGEMENT");
            
            return _headers;

        }

        [Fact]
        public async Task RunFunctionTest()
        {
            //Arrange
            var query = new Dictionary<String, StringValues>();
            query.TryAdd("MessageType", "BOOKING");
            query.TryAdd("TransactionType", "UPDATE");
            query.TryAdd("System", "ORDER_MANAGEMENT");
            query.TryAdd("MessageId", "some guid");
           

            var body = JsonSerializer.Serialize(new {

                Message = "BOOKING",
                System = "ORDER_MANAGEMENT",
                MessageId = "some guid"
            });
        
        

The place I'm stuck is to create mocks for the delegate Func<MessageType,IMessageProcessor> which essentially routes to a specific class and transaction. How can I write the Mock stubs which is so that i can pass those mock objects to my system under test and test for correctness if it was invoked correctly thereby sending Status.OK as result

_sut = new InboundEvent(_serviceProviderMock.Object, _accessTokenValidator.Object);
var result = await _sut.Run(req: HttpRequestSetup(query, body), _loggerMock.Object);
var resultObject = (OkObjectResult)result;

//Assert
Assert.Equal("OK", resultObject.Value);

Things I tried: Creating a delegate mock using the syntax below, however

Mock<Func<MessageType, IMessageProcessor>> _serviceProviderMock = new Mock<Func<MessageType, IMessageProcessor>>();
            _serviceProviderMock.Setup(_ => _(It.IsAny<MessageType>())).Returns(It.IsAny<IMessageProcessor>());

            _sut = new InboundEvent(_serviceProviderMock.Object, _accessTokenValidator.Object);

            var result = await _sut.Run(req: HttpRequestSetup(query, body), _loggerMock.Object);

But still the ProcessMessage in the InboundEvent class fails Object Reference Not set to an instance as the data is null.

question from:https://stackoverflow.com/questions/65935724/writing-mocks-for-delegates-in-an-azure-function-using-moq

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

If InputModel is a POCO with no side-effects then there is no need to mock it. Just create an instance and use that.

No need to use Moq to mock the delegate. Create a delegate to behave as desired for the test and use that

//...

Func<MessageType, IMessageProcessor> _serviceProviderMock = messageType => {

    //messageType can be inspected and a result returned as needed

    return messageProcessorMock.Object; 
};

_sut = new InboundEvent(_serviceProviderMock, _accessTokenValidator.Object);

//...

But how can I verify the delegate is invoked

You can put a boolean flag within the delegate and assert that

boolean delegateInvoked = false;

Func<MessageType, IMessageProcessor> _serviceProviderMock = messageType => {
    delegateInvoked = true;

    //messageType can be inspected and a result returned as needed

    return messageProcessorMock.Object; 
};

// ...

// Assert if delegateInvoked is true

or If the mocked processor is invoked then that by extension would mean that the delegate was invoked, returning said mocked processor.

messageProcessorMock.Verify(_ => _.ProcessMessage(It.IsAny<InputModel>()));

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

56.9k users

...