azure - 使用 Moq 在 Azure 函数中为委托(delegate)编写 Mock

标签 azure unit-testing azure-functions moq xunit

我有一个 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 类,它将包含用于测试 InboundEvent Azure 函数的 Run 方法的测试方法

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

我陷入困境的地方是为委托(delegate) Func 创建模拟,它本质上路由到特定的类和事务。我怎样才能编写模拟 stub 这样我就可以将这些模拟对象传递到正在测试的系统并测试其正确性(如果正确调用),从而发送 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);

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

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 类中的 ProcessMessage 仍然失败,因为数据为空,所以对象引用未设置为实例。

最佳答案

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

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

//...

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

您可以在委托(delegate)中放置一个 bool 标志并断言

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

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

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

关于azure - 使用 Moq 在 Azure 函数中为委托(delegate)编写 Mock,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65935724/

相关文章:

angular - 我在 chrome 中收到错误,提示为 400 bad request for preflight。 Angular + Azure 函数

azure - 在 Azure 应用程序网关上托管多个(假设 5 个)子域的最佳方法是什么?

asp.net-mvc - Microsoft 使用 ASP.NET 成员(member)资格提供商进行单元测试

javascript - 测试 React 函数是用 Jest 调用的

c++ - 重构模板类,使用它的嵌套类作为另一个类的模板参数

c# - 如何确定有害队列消息的原因

python - Azure Functions : Exception while executing function: Functions. x <--- 结果:失败异常:ModuleNotFoundError:没有名为 '_cffi_backend' 的模块

azure - 使用 Azure AD 和 MSAL 的 SPA 应用程序之间的 SSO

sql - 调用存储过程时的性能问题

azure - 如何以编程方式重新启动 Azure 中的应用服务实例?