使用 Moq 为 Azure 函数中的委托编写模拟

Writing Mocks for delegates in an Azure Function using Moq

我有一个 Azure 函数,它基本上是在 HttpRequest 到端点时被调用的。然后,此函数根据有效负载中传递的 CREATE 或 UPDATE 消息调用数据库中的相关部分。

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

ServiceProvider 组件调用 MainBookingProcessor,它根据事务类型“CREATE”或“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();
        }
    }
}

现在是我面临的问题的主要部分。我正在编写一个测试组件来使用 xUnit 和 Moq 测试这个 Azure 函数。为此,我创建了一个 InboundEventTests class,它将包含用于测试 InboundEvent Azure 函数

的 运行 方法的测试方法
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"
            });
        
        

我卡住的地方是为委托 Func 创建模拟,它基本上路由到特定的 class 和事务。我该如何编写 Mock 存根 这样我就可以将这些模拟对象传递给我的被测系统并测试正确性是否被正确调用从而发送 Status.OK 作为结果

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

我尝试过的事情: 但是,使用以下语法创建委托模拟

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

但 InboundEvent class 中的 ProcessMessage 仍然失败对象引用未设置为实例,因为数据为空。

如果 InputModel 是一个没有副作用的 POCO,那么就没有必要模拟它。只需创建一个实例并使用它。

无需使用 Moq 模拟委托。创建一个委托以按照测试所需的方式运行并使用它

//...

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

您可以在委托中放置一个布尔标志并断言

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

或者如果模拟处理器被调用,那么通过扩展将意味着委托被调用,返回所述模拟处理器。

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