<|start_filename|>src/Moq.ILogger/VerifyLogExpressionArgs.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using System;
using System.Linq.Expressions;

// ReSharper disable once CheckNamespace
namespace Moq
{
    internal class VerifyLogExpressionArgs
    {
        public LogLevel LogLevel { get; private set; }
        public string Message { get; private set; }
        public Exception Exception { get; private set; }
        public EventId EventId { get; private set; }
        public object[] MessageArgs { get; private set; }

        public static VerifyLogExpressionArgs From(Expression expression) => new VerifyLogExpressionArgs
        {
            LogLevel = GetLogLevelFrom(expression),
            Message = ExpressionInspector.GetArgOf<string>(expression),
            MessageArgs = Expression.Lambda<Func<object[]>>(ExpressionInspector.GetArgExpressionOf<object[]>(expression)).Compile()(),
            Exception = GetException(expression),
            EventId = GetEventId(expression)
        };

        private static LogLevel GetLogLevelFrom(Expression expression)
        {
            var methodCall = (MethodCallExpression)((LambdaExpression)expression).Body;
            var name = methodCall.Method.Name;
            var logLevel = name switch
            {
                "LogDebug" => LogLevel.Debug,
                "LogInformation" => LogLevel.Information,
                "LogWarning" => LogLevel.Warning,
                "LogError" => LogLevel.Error,
                "LogTrace" => LogLevel.Trace,
                "LogCritical" => LogLevel.Critical,
                _ => throw new NotSupportedException($"A LogLevel for method {methodCall} could not be resolved.")
            };
            return logLevel;
        }

        private static EventId GetEventId(Expression expression) 
            => !(ExpressionInspector.GetArgExpression(expression, c => c.Type == typeof(EventId)) is ConstantExpression eventIdArgExpression)
                ? default
                : (EventId) eventIdArgExpression.Value;
        private static Exception GetException(Expression expression)
            => ExpressionInspector.GetArgExpression(expression, c => typeof(Exception).IsAssignableFrom(c.Type)) switch
            {
                ConstantExpression constantExceptionExpression => constantExceptionExpression.Value as Exception,
                NewExpression newExceptionExpression => Expression.Lambda<Func<Exception>>(newExceptionExpression)
                    .Compile()
                    .Invoke(),
                _ => null
            };
    }
}


<|start_filename|>tests/Moq.ILogger.Tests/Samples/OrdersProcessor.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Xunit;


// ReSharper disable once CheckNamespace
namespace Moq.Tests.Samples
{
    public class PaymentsProcessor
    {
        private readonly IOrdersRepository _ordersRepository;
        private readonly IPaymentService _paymentService;
        private readonly ILogger<PaymentsProcessor> _logger;

        public PaymentsProcessor(IOrdersRepository ordersRepository, 
            IPaymentService paymentService, 
            ILogger<PaymentsProcessor> logger)
        {
            _ordersRepository = ordersRepository;
            _paymentService = paymentService;
            _logger = logger;
        }

        public async Task ProcessOutstandingOrders()
        {
            var outstandingOrders = await _ordersRepository.GetOutstandingOrders();
            foreach (var order in outstandingOrders)
            {
                try
                {
                    var paymentTransaction = await _paymentService.CompletePayment(order);
                    _logger.LogInformation("Order with {orderReference} was paid {at} by {customerEmail}, having {transactionId}", order.OrderReference, paymentTransaction.CreateOn, order.CustomerEmail, paymentTransaction.TransactionId);
                }
                catch (Exception e)
                {
                    _logger.LogWarning(e, "An exception occurred while completing the payment for {orderReference}", order.OrderReference);
                }
            }
            _logger.LogInformation("A batch of {0} outstanding orders was completed", outstandingOrders.Count);
        }
    }

    public interface IPaymentService
    {
        Task<PaymentTransaction> CompletePayment(Order order);
    }

    public interface IOrdersRepository
    {
        Task<IReadOnlyCollection<Order>> GetOutstandingOrders();
    }

    public class PaymentTransaction
    {
        public DateTime CreateOn { get; set; }
        public string TransactionId { get; set; }
    }

    public class Order
    {
        public string OrderReference { get; set; }
        public string CustomerEmail { get; set; }
    }

    public class PaymentsProcessorTests
    {
        [Fact]
        public async Task Processing_outstanding_orders_logs_order_and_transaction_data_for_each_completed_payment()
        {
            // Arrange
            var ordersRepositoryMock = new Mock<IOrdersRepository>();
            ordersRepositoryMock.Setup(c => c.GetOutstandingOrders())
                .ReturnsAsync(GenerateOutstandingOrders(100));

            var paymentServiceMock = new Mock<IPaymentService>();
            paymentServiceMock
                .Setup(c => c.CompletePayment(It.IsAny<Order>()))
                .ReturnsAsync((Order order) => new PaymentTransaction
                {
                    TransactionId = $"TRX-{order.OrderReference}"
                });

            var loggerMock = new Mock<ILogger<PaymentsProcessor>>();

            var sut = new PaymentsProcessor(ordersRepositoryMock.Object, paymentServiceMock.Object, loggerMock.Object);

            // Act
            await sut.ProcessOutstandingOrders();

            // Assert
            loggerMock.VerifyLog(logger => logger.LogInformation("Order with {orderReference} was paid {at} by {customerEmail}, having {transactionId}",
                It.Is<string>(orderReference => orderReference.StartsWith("Reference")),
                It.IsAny<DateTime>(),
                It.Is<string>(customerEmail => customerEmail.Contains("@")),
                It.Is<string>(transactionId => transactionId.StartsWith("TRX"))),
                Times.Exactly(100));
        }

        [Fact]
        public async Task Processing_outstanding_orders_logs_batch_size()
        {
            // Arrange
            var ordersRepositoryMock = new Mock<IOrdersRepository>();
            ordersRepositoryMock.Setup(c => c.GetOutstandingOrders())
                .ReturnsAsync(GenerateOutstandingOrders(100));

            var paymentServiceMock = new Mock<IPaymentService>();
            paymentServiceMock
                .Setup(c => c.CompletePayment(It.IsAny<Order>()))
                .ReturnsAsync((Order order) => new PaymentTransaction
                {
                    TransactionId = $"TRX-{order.OrderReference}"
                });

            var loggerMock = new Mock<ILogger<PaymentsProcessor>>();

            var sut = new PaymentsProcessor(ordersRepositoryMock.Object, paymentServiceMock.Object, loggerMock.Object);

            // Act
            await sut.ProcessOutstandingOrders();

            // Assert
            loggerMock.VerifyLog(c => c.LogInformation("A batch of 100 outstanding orders was completed"));
        }

        [Fact]
        public async Task Processing_outstanding_orders_logs_a_warning_when_payment_fails()
        {
            // Arrange
            var ordersRepositoryMock = new Mock<IOrdersRepository>();
            ordersRepositoryMock.Setup(c => c.GetOutstandingOrders())
                .ReturnsAsync(GenerateOutstandingOrders(2));

            var paymentServiceMock = new Mock<IPaymentService>();
            paymentServiceMock
                .SetupSequence(c => c.CompletePayment(It.IsAny<Order>()))
                .ReturnsAsync(new PaymentTransaction
                {
                    TransactionId = "TRX-1",
                    CreateOn = DateTime.Now.AddMinutes(-new Random().Next(100)),
                })
                .Throws(new Exception("Payment exception"));

            var loggerMock = new Mock<ILogger<PaymentsProcessor>>();

            var sut = new PaymentsProcessor(ordersRepositoryMock.Object, paymentServiceMock.Object, loggerMock.Object);

            // Act
            await sut.ProcessOutstandingOrders();

            // Assert
            loggerMock.VerifyLog(c => c.LogWarning(It.Is<Exception>(paymentException => paymentException.Message.Contains("Payment exception")), "*exception*Reference 2"));
        }

        private static List<Order> GenerateOutstandingOrders(int count) =>
            Enumerable.Range(1, count).Select(i => new Order
            {
                OrderReference = $"Reference {i}",
                CustomerEmail = $"<EMAIL>"
            }).ToList();
    }
}


<|start_filename|>src/Moq.ILogger/VerifyLogUnexpectedException.cs<|end_filename|>
﻿using System;
using System.Runtime.Serialization;

// ReSharper disable once CheckNamespace
namespace Moq
{
    /// <summary>
    /// Encapsulates Moq exceptions and provides extra instructions
    /// </summary>
    [Serializable]
    public class VerifyLogUnexpectedException : Exception
    {
        /// <summary>
        /// Default VerifyLogUnexpectedException constructor
        /// </summary>
        public VerifyLogUnexpectedException()
        {
        }

        /// <summary>
        /// VerifyLogUnexpectedException constructor from a string message
        /// </summary>
        /// <param name="message">The exception message</param>
        public VerifyLogUnexpectedException(string message) : base(message)
        {
        }

        /// <summary>
        /// VerifyLogUnexpectedException constructor from a string message and an inner exception
        /// </summary>
        /// <param name="message">The exception message</param>
        /// <param name="innerException">The inner exception message</param>
        public VerifyLogUnexpectedException(string message, Exception innerException) : base(message, innerException)
        {
        }

        /// <summary>
        /// VerifyLogUnexpectedException used during deserialization
        /// </summary>
        /// <param name="info">Serialization info</param>
        /// <param name="context">Serialization context</param>
        protected VerifyLogUnexpectedException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
    }
}

<|start_filename|>tests/Moq.ILogger.Tests/VerifyLogExtensionsTests.cs<|end_filename|>
using Microsoft.Extensions.Logging;
using System;
using Xunit;
using FluentAssertions;

// ReSharper disable once CheckNamespace
namespace Moq.Tests
{
    public class VerifyLogExtensionsTests
    {
        [Fact]
        public void Verify_log_debug_with_an_expected_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_log_information_with_an_expected_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_log_warning_with_an_expected_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogWarning("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_log_error_with_an_expected_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogError("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogError("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_log_critical_with_an_expected_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogCritical("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogCritical("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_log_level_was_called_but_it_was_not_then_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"Test message\"*");
        }

        [Fact]
        public void Verify_a_message_that_is_not_a_match_then_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("A different message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"A different message\"*");
        }

        [Fact]
        public void Verify_with_a_message_that_differs_by_case_then_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test Message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_formatted_log_message_and_the_param_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message {0}", 1);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message {0}", 1));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_formatted_log_message_and_with_different_param_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message {0}", 1);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message {0}", 2));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"Test message {0}\"*2*");
        }

        [Fact]
        public void Verify_a_formatted_log_message_with_different_parameter_value_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message {0}", 1);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message 2"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"Test message 2\"*");
        }

        [Fact]
        public void Verify_a_message_against_wildcard_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test gibberish message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test*message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_message_with_an_wildcard_but_different_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test gibberish message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test*something"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"Test*something\"*");
        }

        [Fact]
        public void Verify_a_multiline_message_against_wildcard_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation(@"The configuration doesn't define any stages to run.
            Please make sure that at least one stage is set to run.");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("*configuration*"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_message_that_is_part_of_the_log_message_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message with more content");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Test message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(\"Test message\"*");
        }

        [Fact]
        public void Verify_a_structured_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_when_expected_is_formatted_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_when_expected_is_formatted_but_has_a_parameter_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms.", new { Latitude = 25, Longitude = 134 }));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed * in * ms."));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_and_containing_parameters_names_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed*{@Position}*{Elapsed:000}*ms."));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_and_containing_formatted_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = *, Longitude = * } in * ms."));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_and_parameters_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed * in * ms.", new { Latitude = 25, Longitude = 134 }, 34));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_with_parameters_names_and_parameters_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} * {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_using_wildcards_with_parameters_names_and_parameters_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = *, Longitude = * } in * ms.", new { Latitude = 25, Longitude = 134 }, 34));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_message_using_wildcards_with_interpolated_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger<object>>();
            var interpolatedMessageArg = "Arg";
            loggerMock.Object.LogInformation($"Test message {interpolatedMessageArg}");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation($"*{interpolatedMessageArg}*"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_message_using_wildcards_with_interpolated_message_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            var interpolatedMessageArg1 = "Arg 1";
            var interpolatedMessageArg2 = "Arg 2";
            loggerMock.Object.LogInformation($"Test message {interpolatedMessageArg1}");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation($"*{interpolatedMessageArg2}*"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_structured_message_when_parameters_differ_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 0, Longitude = 0 };
            var elapsedMs = 0;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_structured_message_with_fewer_parameters_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 0, Longitude = 0 };
            var elapsedMs = 0;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_structured_message_with_more_parameters_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 0, Longitude = 0 };
            var elapsedMs = 0;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34, true));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_structured_message_with_matching_It_Is_parameters_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.IsAny<It.IsAnyType>(), It.Is<int>(ms => ms == 34)));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_a_structured_message_with_not_matching_It_Is_parameters_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", null, It.Is<int>(ms => ms != 34)));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_a_message_with_method_call_it_verifies()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(GetMessage()));

            act.Should().NotThrow();
        }
        string GetMessage() => "Test message";

        [Fact]
        public void Verify_a_message_with_a_different_one_from_a_method_call_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(GetNotAMessage()));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(VerifyLogExtensionsTests.GetNotAMessage(), new[] {  })*")
                .Which.InnerException!.Message.Should().Match("*logger => logger.Log<It.IsAnyType>(LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => VerifyLogExtensions.CompareMessages(\"Not a test message\", VerifyLogExpression, v.ToString())), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>())*");
        }
        string GetNotAMessage() => "Not a test message";

        [Fact]
        public void Verify_a_wildcard_message_with_method_call_it_verifies()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(GetWildcardMessage()));

            act.Should().NotThrow();
        }
        string GetWildcardMessage() => "*message*";

        [Fact]
        public void Verify_a_wildcard_message_with_a_different_one_from_a_method_call_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(GetWildcardNotAMessage()));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(VerifyLogExtensionsTests.GetWildcardNotAMessage(), new[] {  })*")
                .Which.InnerException!.Message.Should().Match("*logger => logger.Log<It.IsAnyType>(LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => VerifyLogExtensions.CompareMessages(\"*no match*\", VerifyLogExpression, v.ToString())), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>())*");
        }
        string GetWildcardNotAMessage() => "*no match*";

        [Fact]
        public void Verify_a_message_one_constructed_call_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(new string(new[] { 'a' })));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(new string(new[] { a }), new[] {  })*")
                .Which.InnerException!.Message.Should().Match("*logger => logger.Log<It.IsAnyType>(LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => VerifyLogExtensions.CompareMessages(\"a\", VerifyLogExpression, v.ToString())), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>())*");
        }

        [Fact]
        public void Verify_an_error_is_logged_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Something unexpected happened, but will survive."), "Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogWarning(new Exception("Something unexpected happened, but will survive."), "Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_an_error_by_a_condition_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), "Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(It.Is<Exception>(e => e.Message == "Some error message."), "Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_an_error_with_different_messages_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), "Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(It.Is<Exception>(e => e.Message == "Some different message."), "Test message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogWarning(It.Is<Exception>(e => e.Message == \"Some different message.\"), \"Test message\", new[] {  })*");
        }

        [Fact]
        public void Verify_any_error_any_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), "Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(It.IsAny<Exception>(), It.IsAny<string>()));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_null_error_any_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(null, "Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(null, It.IsAny<string>()));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_null_error_any_message_when_error_is_logged_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), "Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(null, It.IsAny<string>()));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogWarning(null*")
                .Which.InnerException!.Message.Should().Match("*logger => logger.Log<It.IsAnyType>(LogLevel.Warning, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => True), null, (Func<It.IsAnyType, Exception, string>)It.IsAny<object>())*");
        }

        [Fact]
        public void Verify_any_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsAny<string>()));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_message_with_matching_criteria_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.Is<string>(msg => msg.Contains("Test"))));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_message_with_no_matching_criteria_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.Is<string>(msg => msg.Contains("Expecting something else"))));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation(It.Is<string>(msg => msg.Contains(\"Expecting something else\")), new[] {  })*");
        }

        [Fact]
        public void Verify_null_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), null);

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(It.IsAny<Exception>(), null));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_null_against_log_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogWarning(new Exception("Some error message."), "Some message");

            Action act = () => loggerMock.VerifyLog(c => c.LogWarning(It.IsAny<Exception>(), null));

            act.Should().ThrowExactly<VerifyLogException>()
               .WithMessage("*.LogWarning(It.IsAny<Exception>(), null, new[] {  })*");
        }

        [Fact]
        public void Verify_it_is_not_null_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsNotNull<string>()));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_it_is_not_null_when_message_is_null_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation(null);

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsNotNull<string>()));

            act.Should().ThrowExactly<VerifyLogException>()
               .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_it_is_regex_message_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsRegex("^(.*)$")));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_it_is_regex_with_method_call_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsRegex(GetRegex())));

            act.Should().NotThrow();
        }
        string GetRegex() => "^(.*)$";

        [Fact]
        public void Verify_it_is_regex_when_message_does_not_match_the_regex_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.LogInformation(It.IsRegex("[0-9]")));

            act.Should().ThrowExactly<VerifyLogException>()
             .WithMessage("*.LogInformation*");
        }

        [Fact]
        public void Verify_event_id_is_logged_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation(new EventId(10, "Order"), "Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation(new EventId(10, "Order"), "Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_event_id_is_logged_when_mismatch_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation((EventId)10, "Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation((EventId)5, "Test message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*5*");
        }

        [Fact]
        public void Verify_event_id_is_logged_when_It_is_used_it_verifies()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation((EventId)10, "Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation(It.Is<EventId>(eid => eid.Id == 10), "Test message"));

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_event_id_is_logged_when_It_is_used_and_mismatch_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation((EventId)10, "Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation(It.Is<EventId>(eid => eid.Id == 5), "Test message"));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*.LogInformation*5*");
        }

        [Fact]
        public void Verify_when_unsupported_extensions_are_used_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.SomeLoggerExtension());

            act.Should().ThrowExactly<NotSupportedException>()
                .WithMessage("Moq.ILogger supports*Microsoft.Extensions.Logging*SomeLoggerExtension*is not one of these*");
        }

        [Fact]
        public void Verify_when_unsupported_extensions_are_used_for_generic_logger_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => c.SomeLoggerExtension());

            act.Should().ThrowExactly<NotSupportedException>()
                .WithMessage("Moq.ILogger supports*Microsoft.Extensions.Logging*SomeLoggerExtension*is not one of these*");
        }

        [Fact]
        public void Verify_when_unexpected_error_happens_throws_an_unexpected_exception_with_an_explanatory_message()
        {
            var loggerMock = new Mock<ILogger>();
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;
            loggerMock.Object.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);

            Action act = () => loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = It.Is<int>(lat => lat == 25), Longitude = 134 }, 34));

            act.Should().ThrowExactly<VerifyLogUnexpectedException>()
                .WithMessage("*https://github.com/adrianiftode/Moq.ILogger/issues/new*")
                .WithInnerException<Exception>();
        }

        [Fact]
        public void Verify_when_unsupported_expression_is_used_it_throws()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => new object());

            act.Should().ThrowExactly<NotSupportedException>()
                .WithMessage("Moq.ILogger supports*Microsoft.Extensions.Logging*A method name could not be resolved*");
        }

        [Fact]
        public void Verify_when_unsupported_unsupported_expression_is_used_for_generic_logger_it_throws()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogInformation("Test message");

            Action act = () => loggerMock.VerifyLog(c => new object());

            act.Should().ThrowExactly<NotSupportedException>()
                .WithMessage("Moq.ILogger supports*Microsoft.Extensions.Logging*A method name could not be resolved*");
        }

        [Fact]
        public void Verify_includes_the_fail_message()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Message"), "We expect to log `Message`.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*We expect to log `Message`.*");
        }

        [Fact]
        public void Verify_includes_the_fail_message_for_generic_logger()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Message"), "We expect to log `Message`.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*We expect to log `Message`.*");
        }

        [Fact]
        public void Verify_uses_Times()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.AtLeast(2));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*");
        }

        [Fact]
        public void Verify_includes_the_fail_message_with_Times()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.AtLeast(2), "We expect to log `Test message` at least twice.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*")
                .WithMessage("*We expect to log `Test message` at least twice.*");
        }

        [Fact]
        public void Verify_uses_Times_for_generic_logger()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.AtLeast(2));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*");
        }

        [Fact]
        public void Verify_includes_the_fail_message_with_Times_for_generic_logger()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.AtLeast(2), "We expect to log `Test message` at least twice.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*")
                .WithMessage("*We expect to log `Test message` at least twice.*");
        }

        [Fact]
        public void Verify_uses_Func_Times()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), () => Times.AtLeast(2));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*");
        }

        [Fact]
        public void Verify_includes_the_fail_message_with_Func_Times()
        {
            var loggerMock = new Mock<ILogger>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), () => Times.AtLeast(2), "We expect to log `Test message` at least twice.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*")
                .WithMessage("*We expect to log `Test message` at least twice.*");
        }

        [Fact]
        public void Verify_uses_Func_Times_for_generic_logger()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), () => Times.AtLeast(2));

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*");
        }

        [Fact]
        public void Verify_includes_the_fail_message_with_Func_Times_for_generic_logger()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), () => Times.AtLeast(2), "We expect to log `Test message` at least twice.");

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock at least 2 times, but was 1 time*")
                .WithMessage("*We expect to log `Test message` at least twice.*");
        }

        [Fact]
        public void Verify_uses_Times_Never()
        {
            var loggerMock = new Mock<ILogger<object>>();

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.Never());

            act.Should().NotThrow();
        }

        [Fact]
        public void Verify_includes_the_fail_message_with_Times_Never()
        {
            var loggerMock = new Mock<ILogger<object>>();
            loggerMock.Object.LogDebug("Test message");

            Action act = () => loggerMock.VerifyLog(logger => logger.LogDebug("Test message"), Times.Never);

            act.Should().ThrowExactly<VerifyLogException>()
                .WithMessage("*Expected invocation on the mock should never have been performed, but was 1 time*");
        }
    }

    internal static class OtherExtensions
    {
        public static void SomeLoggerExtension(this ILogger logger) { }
    }
}


<|start_filename|>src/Moq.ILogger/ExpressionInspector.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Linq.Expressions;

// ReSharper disable once CheckNamespace
namespace Moq
{
    internal static class ExpressionInspector
    {
        internal static T GetArgOf<T>(Expression expression) where T : class
            => (GetArgExpression(expression, c => c.Type == typeof(T)) as ConstantExpression)?.Value as T;

        internal static Expression GetArgExpression(Expression expression, Func<Expression, bool> argPredicate)
        {
            var methodCall = (MethodCallExpression)((LambdaExpression)expression).Body;
            var argExpression = methodCall.Arguments.FirstOrDefault(argPredicate);
            return argExpression;
        }

        internal static Expression GetArgExpressionOf<T>(Expression expression)
            => GetArgExpression(expression, c => c.Type == typeof(T));
    }
}

<|start_filename|>src/Moq.ILogger/VerifyLogExtensions.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

// ReSharper disable once CheckNamespace
namespace Moq
{
    /// <summary>
    /// Encapsulates a collection of extensions methods over Mock ILogger in order to define Moq Verify calls over the ILogger extensions
    /// </summary>
    public static class VerifyLogExtensions
    {
        private const string NullMessageFormatted = "[null]";
        private const string OriginalFormat = "{OriginalFormat}";

        #region VerifyLog API
        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression, string failMessage)
            => Verify(loggerMock, expression, null, null, failMessage);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression, Times times)
            => Verify(loggerMock, expression, times, null, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression, Times times, string failMessage)
            => Verify(loggerMock, expression, times, null, failMessage);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression, Func<Times> times)
            => Verify(loggerMock, expression, null, times, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression, Func<Times> times, string failMessage)
            => Verify(loggerMock, expression, null, times, failMessage);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the ILogger mock.
        /// </summary>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog(this Mock<ILogger> loggerMock, Expression<Action<ILogger>> expression)
            => Verify(loggerMock, expression, null, null, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression)
            => Verify(loggerMock, expression, null, null, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression, string failMessage)
            => Verify(loggerMock, expression, null, null, failMessage);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression, Times times)
            => Verify(loggerMock, expression, times, null, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression, Times times, string failMessage)
            => Verify(loggerMock, expression, times, null, failMessage);


        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression, Func<Times> times)
            => Verify(loggerMock, expression, null, times, null);

        /// <summary>
        /// Verifies that a specific invocation matching the given expression was performed on the generic ILogger mock.
        /// </summary>
        /// <typeparam name="T">The type of the logger category</typeparam>
        /// <param name="loggerMock">The generic ILogger mock object.</param>
        /// <param name="expression">Expression to verify. See the remarks section for the supported expressions.</param>
        /// <param name="times">The number of times a method is expected to be called.</param>
        /// <param name="failMessage">Message to show if verification fails.</param>
        /// <exception cref="VerifyLogException">
        /// The invocation was not performed on the ILogger mock.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The invocation expression was not defined for one of the logging extensions as defined in the <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions"><see cref="LoggerExtensions"/> class</see> from the <see href="https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/">Microsoft.Extensions.Logging.Abstractions</see> package.
        /// </exception>
        /// <example>
        /// <code>
        ///     loggerMock.VerifyLog(logger =&gt; logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));
        /// </code>
        /// </example>
        /// <remarks>
        /// VerifyLog can interpret only .NET Core ILogger extensions, thus the expression argument must contain only LogInformation/LogDebug, etc calls.
        /// Any argument in the method call expression can be expressed as:
        /// <list type="bullet">
        ///     <item>an It.Is* Moq construct: logger => logger.LogInformation(It.IsAny&lt;string&gt;()</item>
        ///     <item>a constant: logger => logger.LogInformation("Test message")</item>
        /// </list>
        /// When the message argument is provided as a constant in the expression, then VerifyLog expects for the following scenarios:
        /// <list type="bullet">
        ///     <item>it can assume the message is the formatted one, like "Processed { Latitude = 25, Longitude = 134 } in 034 ms.". If so, VerifyLog transforms each ILogger.Log invocation and compares the result with the given message</item>
        ///     <item>it can assume the message is the actual message format, like "Processed {@Position} in {Elapsed:000} ms.". If so, it compares the message with the message formats encountered during each ILogger.Log invocation and also evaluates the invocations arguments to be as the ones provided in the VerifyLog expression</item>
        ///     <item>it can interpret wildcard messages, like "Processed { Latitude = *, Longitude = * } in * ms." or "Processed {@Position}*{Elapsed:000} ms.". If so, it compares the wildcard message with either the messages formats or the transformed messages encountered during each ILogger.Log invocation. When the message is the actual format and the format arguments are provided, then these are compared too.</item>
        /// </list>
        /// </remarks>
        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, Expression<Action<ILogger>> expression, Func<Times> times, string failMessage)
            => Verify(loggerMock, expression, null, times, failMessage);
        #endregion

        private static void Verify<T>(Mock<T> loggerMock, Expression<Action<ILogger>> expression, Times? times, Func<Times> timesFunc,
            string failMessage) where T : class
        {
            try
            {
                GuardVerifyExpressionIsForLoggerExtensions(expression);

                var verifyLogExpression = VerifyLogExpression.From(expression);
                var verifyExpression = CreateMoqVerifyExpressionFrom<T>(verifyLogExpression);
                try
                {
                    if (timesFunc != null)
                    {
                        loggerMock.Verify(verifyExpression, timesFunc, failMessage);
                    }
                    else if (times.HasValue)
                    {
                        loggerMock.Verify(verifyExpression, times.Value, failMessage);
                    }
                    else
                    {
                        loggerMock.Verify(verifyExpression, failMessage);
                    }
                }
                catch (MockException ex)
                {
                    throw new VerifyLogException(BuildExceptionMessage(ex, expression), ex);
                }
            }
            catch (NotSupportedException nse) when (nse.Message.Contains("supports only specific Logging extensions"))
            {
                throw;
            }
            catch (VerifyLogException)
            {
                throw;
            }
            catch (Exception exception)
            {
                var message = "Moq.ILogger found an unexpected exception." +
                              Environment.NewLine +
                              Environment.NewLine +
                              "Please open an issue at https://github.com/adrianiftode/Moq.ILogger/issues/new, provide the exception details and a sample code if possible." +
                              Environment.NewLine +
                              "Bellow will follow the unexpected exception details." +
                              Environment.NewLine;
                throw new VerifyLogUnexpectedException(message, exception);
            }
        }

        private static Expression<Action<T>> CreateMoqVerifyExpressionFrom<T>(VerifyLogExpression verifyLogExpression)
        {
            var logLevelExpression = CreateLogLevelExpression(verifyLogExpression);
            var eventIdExpression = CreateEventIdExpression(verifyLogExpression);
            var exceptionExpression = CreateExceptionExpression(verifyLogExpression);
            var messageExpression = CreateMessageExpression(verifyLogExpression);
            var formatterExpression = CreateFormatterExpression();

            var loggerParameter = Expression.Parameter(typeof(T), "logger");
            var logMethodInfo = typeof(ILogger).GetMethod(nameof(ILogger.Log))!.MakeGenericMethod(typeof(It.IsAnyType));
            var logCallExpression = Expression.Call(loggerParameter, logMethodInfo,
                logLevelExpression,
                eventIdExpression,
                messageExpression,
                exceptionExpression,
                formatterExpression);

            var verifyExpression = Expression.Lambda<Action<T>>(logCallExpression, loggerParameter);
            return verifyExpression;
        }

        private static Expression CreateLogLevelExpression(VerifyLogExpression verifyLogExpression)
            => Expression.Constant(verifyLogExpression.Args.LogLevel);

        private static Expression CreateFormatterExpression()
            => Expression.Convert(BuildItIsAnyExpression<object>(), typeof(Func<It.IsAnyType, Exception, string>));

        private static Expression CreateEventIdExpression(VerifyLogExpression verifyLogExpression)
            => verifyLogExpression.EventIdExpression ?? BuildItIsAnyExpression<EventId>();

        private static Expression CreateExceptionExpression(VerifyLogExpression verifyLogExpression)
        {
            if (verifyLogExpression.ExceptionExpression == null)
            {
                return BuildItIsAnyExpression<Exception>();
            }

            var exception = verifyLogExpression.Args.Exception;
            if (exception == null)
            {
                return verifyLogExpression.ExceptionExpression;
            }

            // an Expression arg is given, create an It.Is<Exception>(e => Compare(e, arg))
            // build It.Is(CompareExceptions(exception)),
            var exceptionParam = Expression.Parameter(typeof(Exception));
            var exceptionConstantExpression = Expression.Constant(exception, typeof(Exception));
            var compareExceptionsCallExpression = Expression.Call(typeof(VerifyLogExtensions), nameof(CompareExceptions), null, exceptionConstantExpression, exceptionParam);
            var compareExpression = Expression.Lambda<Func<Exception, bool>>(compareExceptionsCallExpression, exceptionParam);
            var compareExceptionQuoteExpression = Expression.Quote(compareExpression);
            var itIsExceptionExpression = Expression.Call(typeof(It), "Is", new[] { typeof(Exception) }, compareExceptionQuoteExpression);
            return itIsExceptionExpression;
        }

        private static MethodCallExpression CreateMessageExpression(VerifyLogExpression verifyLogExpression)
        {
            var messageExpression = verifyLogExpression.MessageExpression;

            var vParam = Expression.Parameter(typeof(object), "v");
            var tParam = Expression.Parameter(typeof(Type), "t");

            Expression<Func<object, Type, bool>> compareExpression = null;
            switch (messageExpression)
            {
                case ConstantExpression messageArgConstant:
                    {
                        var messageConstantExpression = Expression.Constant(messageArgConstant.Value, typeof(string));
                        var verifyLogConstantExpression = Expression.Constant(verifyLogExpression, typeof(VerifyLogExpression));
                        var compareMessagesCallExpression = Expression.Call(typeof(VerifyLogExtensions), nameof(CompareMessages), null, messageConstantExpression, verifyLogConstantExpression, vParam);
                        compareExpression = Expression.Lambda<Func<object, Type, bool>>(compareMessagesCallExpression, vParam, tParam);
                        break;
                    }
                case MethodCallExpression methodCallExpression:
                    {
                        var methodName = methodCallExpression.Method.Name;
                        switch (methodName)
                        {
                            // It.IsAny<string>()
                            case "IsAny":
                                {
                                    // build (v, t) => true
                                    var trueExpression = Expression.Constant(true);
                                    compareExpression = Expression.Lambda<Func<object, Type, bool>>(trueExpression, vParam, tParam);
                                    break;
                                }
                            // It.Is<string>(msg => msg.Contains("Test"))
                            case "Is":
                                {
                                    // build It.Is<It.IsAnyType>((v, t) => messagePredicate(v.ToString())
                                    var messagePredicate = ((UnaryExpression)methodCallExpression.Arguments.First()).Operand;
                                    if (messagePredicate is Expression<Func<string, bool>> stringMessagePredicate)
                                    {
                                        var vParamToStringExpression = CreatevParamToStringExpression();
                                        var invokeStringMessagePredicateExpression = Expression.Invoke(stringMessagePredicate, vParamToStringExpression);
                                        compareExpression = Expression.Lambda<Func<object, Type, bool>>(invokeStringMessagePredicateExpression, vParam, tParam);
                                    }

                                    break;
                                }
                            // It.IsNotNull<string>())
                            case "IsNotNull":
                                {
                                    // build (v, t) => v.ToString() != "[null]"
                                    var vParamToStringExpression = CreatevParamToStringExpression();
                                    var nullConstantExpression = Expression.Constant(NullMessageFormatted);
                                    var notNullConstantExpression = Expression.NotEqual(vParamToStringExpression, nullConstantExpression);
                                    compareExpression = Expression.Lambda<Func<object, Type, bool>>(notNullConstantExpression, vParam, tParam);
                                    break;
                                }
                            // It.IsRegex(pattern))
                            case "IsRegex":
                                {
                                    // build (v, t) => Regex.IsMatch(v.ToString(), pattern, RegexOptions.IgnoreCase)
                                    var pattern = Expression.Lambda(methodCallExpression.Arguments[0]).Compile().DynamicInvoke();
                                    var patternConstantExpression = Expression.Constant(pattern);
                                    var regexOptionsConstantExpression = Expression.Constant(RegexOptions.IgnoreCase);
                                    var vParamToStringExpression = CreatevParamToStringExpression();
                                    var callRegexIsMatchExpression = Expression.Call(typeof(Regex), nameof(Regex.IsMatch), null, vParamToStringExpression, patternConstantExpression, regexOptionsConstantExpression);
                                    compareExpression = Expression.Lambda<Func<object, Type, bool>>(callRegexIsMatchExpression, vParam, tParam);
                                    break;
                                }
                            // GetSomeMessage(a, b, c, ....)
                            default:
                                {
                                    //build (v, t) => Compare(v.ToString(), GetSomeMessage(a, b, c, ....))
                                    compareExpression = CreateCompareLambdaFrom(methodCallExpression);
                                    break;
                                }
                        }
                        break;
                    }
                default:
                    {
                        // build (v, t) => Compare(v.ToString(), /*inject the expression here*/)
                        compareExpression = CreateCompareLambdaFrom(messageExpression);
                        break;
                    }
            }

            var compareMessageQuoteExpression = Expression.Quote(compareExpression);
            var itIsMessageExpression = Expression.Call(typeof(It), "Is", new[] { typeof(It.IsAnyType) }, compareMessageQuoteExpression);
            return itIsMessageExpression;

            Expression<Func<object, Type, bool>> CreateCompareLambdaFrom(Expression methodCallExpression)
            {
                var vParamToStringExpression = Expression.Call(vParam, typeof(object).GetMethod(nameof(ToString))!);
                var verifyLogConstantExpression = Expression.Constant(verifyLogExpression, typeof(VerifyLogExpression));

                var compareMessagesCallExpression = Expression.Call(typeof(VerifyLogExtensions), nameof(CompareMessages), null,
                    methodCallExpression, verifyLogConstantExpression, vParamToStringExpression);
                compareExpression = Expression.Lambda<Func<object, Type, bool>>(compareMessagesCallExpression, vParam, tParam);
                return compareExpression;
            }

            MethodCallExpression CreatevParamToStringExpression()
            {
                var vParamToStringExpression = Expression.Call(vParam, typeof(object).GetMethod(nameof(ToString))!);
                return vParamToStringExpression;
            }
        }

        private static Expression BuildItIsAnyExpression<T>()
            => Expression.Call(typeof(It), "IsAny", new[] { typeof(T) });

        private static void GuardVerifyExpressionIsForLoggerExtensions(Expression expression)
        {
            ////https://github.com/dotnet/runtime/blob/e3ffd343ad5bd3a999cb9515f59e6e7a777b2c34/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerExtensions.cs
            var methodCall = (expression as LambdaExpression)?.Body as MethodCallExpression;
            var methodIsaMsLoggerExtensions = methodCall?.Method.ReflectedType == typeof(LoggerExtensions);
            var methodName = methodCall?.Method.Name;
            var supportedMethods = new[]
            {
                nameof(LoggerExtensions.LogCritical),
                nameof(LoggerExtensions.LogDebug),
                nameof(LoggerExtensions.LogError),
                nameof(LoggerExtensions.LogInformation),
                nameof(LoggerExtensions.LogWarning),
                nameof(LoggerExtensions.LogTrace)
            };

            if (!methodIsaMsLoggerExtensions || methodName is null || !supportedMethods.Contains(methodName))
            {
                var message = $"{nameof(Moq)}.{nameof(ILogger)} supports only specific Logging extensions " +
                              "as defined in the <a href=\"https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions\">LoggerExtensions class</a> " +
                              "form the <a href=\"https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/\">\"Microsoft.Extensions.Logging.Abstractions\"</a> package. " +
                              $"The Log use case must be on of ({string.Join(", ", supportedMethods)}). ";

                if (!string.IsNullOrEmpty(methodName))
                {
                    message += $"{Environment.NewLine}" +
                               $"{Environment.NewLine}" +
                               $"The resolved method `{methodName}` in the verify expression is not one of these.";
                }
                else
                {
                    message += $"{Environment.NewLine}" +
                               $"{Environment.NewLine}" +
                               "A method name could not be resolved from the verify expression.";
                }

                throw new NotSupportedException(message);
            }
        }

        private static bool CompareExceptions(Exception exceptionA, Exception exceptionB)
        {
            if (exceptionA == null || exceptionB == null)
            {
                return false;
            }

            if (ReferenceEquals(exceptionA, exceptionB))
            {
                return true;
            }

            return exceptionA.Message == exceptionB.Message && exceptionA.GetType() == exceptionB.GetType();
        }

        private static bool CompareMessages(string expectedMessageFormat, VerifyLogExpression verifyLogExpression, object actualMessageValues)
        {
            var actualMessageFormatted = actualMessageValues.ToString();

            if (expectedMessageFormat == null && actualMessageFormatted == NullMessageFormatted)
            {
                return true;
            }

            if (expectedMessageFormat == null || actualMessageFormatted == NullMessageFormatted)
            {
                return false;
            }

            if (!(actualMessageValues is IReadOnlyList<KeyValuePair<string, object>>))
            {
                return actualMessageFormatted.IsWildcardMatch(expectedMessageFormat);
            }

            var (actualMessageFormat, actualMessageArgs) = ExtractActualMessageFormatAndArgs(actualMessageValues);
            var (expectedMessageFormattedWithSuccess, expectedMessageFormatted) = TryFormatLogValues(expectedMessageFormat, verifyLogExpression.Args.MessageArgs);

            if (expectedMessageFormat.IsWildcard())
            {
                if (verifyLogExpression.HasExpectedMessageArgs)
                {
                    return actualMessageFormat.IsWildcardMatch(expectedMessageFormat) && MessageArgsMatch(verifyLogExpression.MessageArgsExpression, actualMessageArgs);
                }

                if (actualMessageFormat.IsWildcardMatch(expectedMessageFormat)
                    || expectedMessageFormattedWithSuccess && actualMessageFormatted.IsWildcardMatch(expectedMessageFormatted))
                {
                    return true;
                }
            }
            else
            {
                if (verifyLogExpression.HasExpectedMessageArgs)
                {
                    return actualMessageFormat.EqualsIgnoreCase(expectedMessageFormat) && MessageArgsMatch(verifyLogExpression.MessageArgsExpression, actualMessageArgs);
                }

                if (actualMessageFormat.EqualsIgnoreCase(expectedMessageFormat)
                    || expectedMessageFormattedWithSuccess && actualMessageFormatted.EqualsIgnoreCase(expectedMessageFormatted))
                {
                    return true;
                }
            }

            return false;
        }

        private static (string fromat, object[] args) ExtractActualMessageFormatAndArgs(object actualMessageValues)
        {
            var actualMessage = (IReadOnlyList<KeyValuePair<string, object>>)actualMessageValues;
            var actualMessageFormat = actualMessage.First(c => c.Key == OriginalFormat).Value.ToString();
            var actualMessageArgs = actualMessage.Where(c => c.Key != OriginalFormat).Select(c => c.Value).ToArray();
            return (actualMessageFormat, actualMessageArgs);
        }

        private static (bool success, string formatted) TryFormatLogValues(string format, object[] arguments)
        {
            try
            {
                return (true, FormatLogValues(format, arguments));
            }
            catch (FormatException)
            {
                return (false, null);
            }
        }

        private static string FormatLogValues(string format, object[] arguments)
        {
            //https://github.com/dotnet/runtime/blob/e3ffd343ad5bd3a999cb9515f59e6e7a777b2c34/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/FormattedLogValues.cs#L30
            var formattedLogValuesType = typeof(LoggerExtensions).Assembly.GetTypes()
                .First(c => c.Name == "FormattedLogValues");

            var formattedLogValues = Activator.CreateInstance(formattedLogValuesType,
                BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, new object[] { format, arguments },
                null);
            return formattedLogValues.ToString();
        }

        private static bool MessageArgsMatch(Expression expectedMessageArgsExpression, object[] actualArgs)
        {
            // see this Test for reference https://github.com/moq/moq4/blob/61f420f3d44527ce652883ce857fc8b3bdabafca/tests/Moq.Tests/Matchers/ParamArrayMatcherFixture.cs#L19

            // create Expression<> _ = x => x.Method(expectedMessageArgsExpression);
            //

            var xParameter = Expression.Parameter(typeof(IX), "x");
            var instance = Expression.Constant(new X(), typeof(IX));
            var methodCallExpression = Expression.Call(instance, typeof(IX).GetMethod("Method")!, expectedMessageArgsExpression);
            var methodCallLambda = Expression.Lambda(methodCallExpression, xParameter);
            var parameter = typeof(IX).GetMethod("Method")!.GetParameters().Single();

            // Execute the following Moq code
            //          var (matcher, _) = MatcherFactory.CreateMatcher(expr, parameter);
            //          matcher.Matches(actualArgs, typeof(object[]))
            //

            var matcherFactoryType = typeof(Mock).Assembly.GetTypes().First(c => c.Name == "MatcherFactory");
            var createMatcherMethod =
                matcherFactoryType.GetMethod("CreateMatcher", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(Expression), typeof(ParameterInfo) }, null);

            // result is typeof(Pair<IMatcher, Expression>); Pair is a type from Moq, having two fields Item1, Item2
            // we need Item1
            var matcherResult = createMatcherMethod!.Invoke(null, new object[] { ((MethodCallExpression)methodCallLambda.Body).Arguments.Single(), parameter });
            var resultType = matcherResult.GetType();
            var matcherField = resultType.GetField("Item1");
            var matcher = matcherField.GetValue(matcherResult);
            var matcherType = matcher.GetType();

            // invoke matcher.Matches(actualArgs, typeof(object[]))
            var matchesMethod =
                matcherType.GetMethod("Matches", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object[]), typeof(Type) }, null);
            var isMatch = (bool)matchesMethod!.Invoke(matcher, new object[] { actualArgs, typeof(object[]) });
            return isMatch;
        }

        private static string BuildExceptionMessage(MockException ex, Expression expression)
        {
            var stringBuilderExtensionsType = typeof(Mock).Assembly.GetTypes().First(c => c.Name == "StringBuilderExtensions");
            var appendExpressionMethod =
                stringBuilderExtensionsType.GetMethod("AppendExpression", BindingFlags.Static | BindingFlags.Public);
            var stringBuilder = new StringBuilder();
            appendExpressionMethod!.Invoke(null, new object[] { stringBuilder, expression });
            var expressionText = stringBuilder.ToString();

            var moqIndications = ex.Message.Split(':')[0];

            return $"{moqIndications}: {expressionText}" +
                   $"{Environment.NewLine}" +
                   $"{Environment.NewLine}";
        }
    }

    internal interface IX { void Method(params object[] args); }
    internal class X : IX { public void Method(params object[] args) { throw new NotSupportedException("This method should never be called, only inspected."); } }
}


<|start_filename|>tests/Moq.ILogger.Tests/Samples/SomeClassTests.cs<|end_filename|>
using Microsoft.Extensions.Logging;
using System;
using Xunit;

// ReSharper disable once CheckNamespace
namespace Moq.Tests.Samples
{
    public class SomeClass
    {
        private readonly ILogger<SomeClass> _logger;
        public SomeClass(ILogger<SomeClass> logger) => _logger = logger;

        public void LoggingInformation()
            => _logger.LogInformation("This operation is successful.");

        public void LoggingWarning(string name)
            => _logger.LogWarning(new ArgumentException("The given name is not ok", nameof(name)), "This operation failed, but let's log an warning only");

        public void LoggingError(string name)
            => _logger.LogError(new ArgumentException("The given name is not ok", nameof(name)), $"This operation failed for name {name}");

        public void SemanticLogging()
        {
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;

            _logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);
        }

        public void MultipleLoggingCalls()
        {
            var position = new { Latitude = 25, Longitude = 134 };
            var elapsedMs = 34;

            _logger.LogInformation("Processed {@Position} in {Elapsed:000} ms with success.", position, elapsedMs);
            _logger.LogInformation("Processed {@Position} in {Elapsed:000} ms with failure.", position, elapsedMs);
        }
    }

    public class SomeClassTests
    {
        [Fact]
        public void Verify_log_information_with_a_message()
        {
            var loggerMock = new Mock<ILogger<SomeClass>>();
            var sut = new SomeClass(loggerMock.Object);

            sut.LoggingInformation();

            loggerMock.VerifyLog(logger => logger.LogInformation("This operation is successful."));
            loggerMock.VerifyLog(logger => logger.LogInformation((EventId)0, "This operation is successful."));
            loggerMock.VerifyLog(logger => logger.LogInformation("This * is successful."));
            loggerMock.VerifyLog(logger => logger.LogInformation(It.Is<string>(msg => msg.Length > 5)));
            loggerMock.VerifyLog(logger => logger.LogInformation(It.IsAny<string>()));
            loggerMock.VerifyLog(logger => logger.LogInformation(It.IsNotNull<string>()));
            loggerMock.VerifyLog(logger => logger.LogInformation(It.IsRegex(".*")));
        }

        [Fact]
        public void Verify_errors()
        {
            var loggerMock = new Mock<ILogger<SomeClass>>();
            var sut = new SomeClass(loggerMock.Object);

            sut.LoggingWarning(null);

            loggerMock.VerifyLog(logger => logger.LogWarning(It.IsAny<ArgumentException>(), It.IsAny<string>()));
            loggerMock.VerifyLog(logger => logger.LogWarning(It.Is<ArgumentException>(ex => ex.ParamName == "name"), "*failed*"));
            // ReSharper disable once RedundantCast
            loggerMock.VerifyLog(logger => logger.LogWarning((EventId)0, It.IsAny<ArgumentException>(), "*failed*"));
            loggerMock.VerifyLog(logger => logger.LogWarning(It.IsAny<EventId>(), It.IsAny<ArgumentException>(), "*failed*"));
            // ReSharper disable once NotResolvedInText
            loggerMock.VerifyLog(logger => logger.LogWarning(It.IsAny<EventId>(), new ArgumentException("The given name is not ok", "name"), "*failed*"));
        }

        [Fact]
        public void Verify_exceptions_logged_as_errors()
        {
            var name = "Adrian";
            var loggerMock = new Mock<ILogger<SomeClass>>();
            var sut = new SomeClass(loggerMock.Object);

            sut.LoggingError(name);

            loggerMock.VerifyLog(logger => logger.LogError(It.Is<ArgumentException>(ex => ex.ParamName == "name"), $"*{name}*"));
        }

        [Fact]
        public void Verify_semantic_logging()
        {
            var loggerMock = new Mock<ILogger<SomeClass>>();
            var sut = new SomeClass(loggerMock.Object);

            sut.SemanticLogging();

            loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms."));

            loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));
            loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position} in {Elapsed:000} ms.", It.IsAny<It.IsAnyType>(), It.IsAny<int>()));

            loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = *, Longitude = * } in * ms."));
            loggerMock.VerifyLog(logger => logger.LogInformation("Processed * in * ms."));
            loggerMock.VerifyLog(logger => logger.LogInformation("Processed*{@Position}*{Elapsed:000}*ms."));

            loggerMock.VerifyLog(logger => logger.LogInformation("Processed * in * ms.", It.IsAny<It.IsAnyType>(), It.IsAny<int>()));
            loggerMock.VerifyLog(logger => logger.LogInformation("Processed {@Position}*{Elapsed:000} ms.", new { Latitude = 25, Longitude = 134 }, 34));
        }

        [Fact]
        public void Verify_multiple_logging_calls()
        {
            var loggerMock = new Mock<ILogger<SomeClass>>();
            var sut = new SomeClass(loggerMock.Object);

            sut.MultipleLoggingCalls();

            loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms with failure."));
            loggerMock.VerifyLog(logger => logger.LogInformation("Processed { Latitude = 25, Longitude = 134 } in 034 ms with*"), Times.Exactly(2));
        }
    }
}

<|start_filename|>tests/Moq.ILogger.Tests/VerifyLogUnexpectedExceptionTests.cs<|end_filename|>
using System;
using FluentAssertions;
using Xunit;

// ReSharper disable once CheckNamespace
namespace Moq.Tests
{
    public class VerifyLogUnexpectedExceptionTests
    {
        [Fact]
        public void Ctor_DoesNotThrow()
        {
            Action act = () => new VerifyLogUnexpectedException();

            act.Should().NotThrow();
        }

        [Fact]
        public void Ctor_WhenMessage_DoesNotThrow()
        {
            Action act = () => new VerifyLogUnexpectedException("Some message");

            act.Should().NotThrow();
        }

        [Fact]
        public void Ctor_WhenInnerException_DoesNotThrow()
        {
            Action act = () => new VerifyLogUnexpectedException("Some message", new Exception());

            act.Should().NotThrow();
        }

        [Fact]
        public void Exception_ShouldBeBinarySerializable()
        {
            var sut = new VerifyLogUnexpectedException("Some message", new Exception());

            sut.Should().BeBinarySerializable();
        }
    }
}

<|start_filename|>src/Moq.ILogger/VerifyLogExpression.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using System;
using System.Linq.Expressions;

// ReSharper disable once CheckNamespace
namespace Moq
{
    internal class VerifyLogExpression
    {
        public VerifyLogExpressionArgs Args { get; private set; }
        public Expression MessageExpression { get; private set; }
        public Expression ExceptionExpression { get; private set; }
        public Expression EventIdExpression { get; private set; }
        public Expression MessageArgsExpression { get; private set; }
        public bool HasExpectedMessageArgs => ((NewArrayExpression) MessageArgsExpression).Expressions.Count > 0;

        public static VerifyLogExpression From(Expression expression) =>
            new VerifyLogExpression
            {
                ExceptionExpression = ExpressionInspector.GetArgExpression(expression, c => typeof(Exception).IsAssignableFrom(c.Type)),
                EventIdExpression = ExpressionInspector.GetArgExpressionOf<EventId>(expression),
                MessageExpression = ExpressionInspector.GetArgExpressionOf<string>(expression),
                MessageArgsExpression = ExpressionInspector.GetArgExpressionOf<object[]>(expression),
                Args = VerifyLogExpressionArgs.From(expression)
            };
    }
}


<|start_filename|>src/Moq.ILogger/VerifyLogException.cs<|end_filename|>
﻿using System;
using System.Runtime.Serialization;

// ReSharper disable once CheckNamespace
namespace Moq
{
    /// <summary>
    /// Encapsulates Moq exceptions and provides extra instructions
    /// </summary>
    [Serializable]
    public class VerifyLogException : Exception
    {
        /// <summary>
        /// Default VerifyLogException constructor
        /// </summary>
        public VerifyLogException()
        {
        }

        /// <summary>
        /// VerifyLogException constructor from a string message
        /// </summary>
        /// <param name="message">The exception message</param>
        public VerifyLogException(string message) : base(message)
        {
        }

        /// <summary>
        /// VerifyLogException constructor from a string message and an inner exception
        /// </summary>
        /// <param name="message">The exception message</param>
        /// <param name="innerException">The inner exception message</param>
        public VerifyLogException(string message, Exception innerException) : base(message, innerException)
        {
        }

        /// <summary>
        /// VerifyLogException used during deserialization
        /// </summary>
        /// <param name="info">Serialization info</param>
        /// <param name="context">Serialization context</param>
        protected VerifyLogException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
    }
}

<|start_filename|>src/Moq.ILogger/WildcardComparison.cs<|end_filename|>
﻿using System;
using System.Text.RegularExpressions;

// ReSharper disable once CheckNamespace
namespace Moq
{
    internal static class WildcardComparison
    {
        internal static bool IsWildcardMatch(this string source, string wildcard)
        {
            if (string.IsNullOrEmpty(source))
            {
                throw new ArgumentException("Source cannot be null or an empty string.", nameof(source));
            }

            if (string.IsNullOrEmpty(wildcard))
            {
                throw new ArgumentException("Wildcard cannot be null or an empty string.", nameof(wildcard));
            }

            var pattern = WildcardToRegular(wildcard);
            return Regex.IsMatch(source, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);

            static string WildcardToRegular(string value)
                => "^" + Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*") + "$";
        }

        internal static bool EqualsIgnoreCase(this string source, string other)
        {
            if (string.IsNullOrEmpty(source) && string.IsNullOrEmpty(other))
            {
                return true;
            }

            if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(other))
            {
                return false;
            }

            return string.Equals(source, other, StringComparison.OrdinalIgnoreCase);
        }

        internal static bool IsWildcard(this string source)
        {
            if (string.IsNullOrEmpty(source))
            {
                return false;
            }

            if (source[0] == '*')
            {
                return true;
            }

            for (var i = 1; i < source.Length; i++)
            {
                if (source[i] == '*' && source[i - 1] != '\\')
                {
                    return true;
                }
            }

            return false;
        }
    }
}
