2

我正在为自己做一个有趣的项目来学习 blazor、许多核心概念和一般实践。

我正在尝试为 FakeItEasy 实现记录器扩展,以便更轻松地测试 Microsoft 的 ILogger。基于https://dev.azure.com/BrideButler/Bride%20Butler%20Open%20Source/_git/FakeItEasy.DotnetCore.LoggerExtensions?path=%2FFakeItEasy.DotnetCore.LoggerExtensions%2FLoggerExtensions.cs但现在正试图让它工作对于.NET 5。现在这FormattedLogValues是内部代码不再起作用了。

目标是在我的测试中有这样的东西(而不是使用 Moq) _logger.VerifyLog(LogLevel.Information, "Get Weather Called").MustHaveHappenedOnceExactly(); 或者A.CallTo(() => _logger.Log(LogLevel.Information, "Get Weather Called").MustHaveHappenedOnceExactly();它不必有语法糖,但我想让它与 FakeItEasy 而不是 Moq 一起工作。

以下是所有涉及的代码。Microsoft.Extensions.Logging.ILogger.Log当然,我在第一行1[System.Object] vsMicrosoft.Extensions.Logging.ILogger 1[Microsoft.Extensions.Logging.FormattedLogValues]`中看到了一些差异,1[Server.Controllers.WeatherForecastController].Log但我不知道如何“解决”这个问题,因为我不完全理解出了什么问题。

我的问题显然是,我应该怎么做才能解决它,但也很好奇我缺少什么部分。

我的错误如下:

  Assertion failed for the following call:
        Microsoft.Extensions.Logging.ILogger.Log`1[System.Object]
        (
            logLevel: Information,
            eventId: <Ignored>, 
            state: <e => e.ToString().Contains(value(Server.Tests.Extensions.LoggerExtensions+<>c__DisplayClass2_0`1[Server.Controllers.WeatherForecastController]).message)>,
            exception: <Ignored>, 
            formatter: <Ignored>
        )
      Expected to find it once exactly but didn't find it among the calls:
        1: Microsoft.Extensions.Logging.ILogger`1[Server.Controllers.WeatherForecastController].Log`1[Microsoft.Extensions.Logging.FormattedLogValues]
        (
            logLevel: Information,
            eventId: 0,
            state: [[{OriginalFormat}, Get Weather Called]],
            exception: NULL,
            formatter: System.Func`3[Microsoft.Extensions.Logging.FormattedLogValues,System.Exception,System.String]
      )

测试:(使用 xUnit、FakeItEasy、Fixture)

记录器扩展

public static class LoggerExtensions
    {
        public static void VerifyLogMustHaveHappened<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            try
            {
                logger.VerifyLog(level, message)
                    .MustHaveHappened();
            }
            catch (Exception e)
            {
                throw new ExpectationException($"while verifying a call to log with message: \"{message}\"", e);
            }
        }

        public static void VerifyLogMustNotHaveHappened<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            try
            {
                logger.VerifyLog(level, message)
                    .MustNotHaveHappened();
            }
            catch (Exception e)
            {
                throw new ExpectationException($"while verifying a call to log with message: \"{message}\"", e);
            }
        }

        public static IVoidArgumentValidationConfiguration VerifyLog<T>(this ILogger<T> logger, LogLevel level,
            string message)
        {
            return A.CallTo(() => logger.Log(
                    level, 
                    A<EventId>._,
                    A<object>.That.Matches(e => e.ToString().Contains(message)),
                    A<Exception>._,
                    A<Func<object, Exception, string>>._)
                );
        }
    }

测试构造函数:

private readonly IFixture _fixture;
private readonly ILogger<WeatherForecastController> _logger;

public WeatherForecastControllerTests()
{
    _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization());
    _logger = _fixture.Freeze<Fake<ILogger<WeatherForecastController>>>().FakedObject;
}

测试:

[Fact]
public void WeatherForecast_Get_Should_Log()
{
    // Arrange
    var weatherController = new WeatherForecastController(_logger);

    // Act
    weatherController.Get();

    // Assert
    _logger.VerifyLog(LogLevel.Information, "Get Weather Called").MustHaveHappenedOnceExactly();
}

控制器:

public class WeatherForecastController : ControllerBase
{
    private readonly ILogger _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        // Left out other code then logging
        _logger.Log(LogLevel.Information, "Get Weather Called");
    }
}

更新: 找到的选项

仅测试是否使用正确的日志级别调用了 logger:

   public static IVoidArgumentValidationConfiguration VerifyLog<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            return A.CallTo(logger).Where(call => call.Method.Name == "Log" && call.GetArgument<LogLevel>(0) == level);
        }

切换到最小起订量:

https://stackoverflow.com/a/59182490/1112413
https://stackoverflow.com/a/56728528/1112413
4

1 回答 1

3

阅读https://stackoverflow.com/users/4728685/pavel-anikhouski对帖子的评论后。我“明白”出了什么问题。去了无数的话题,并提出了一个“有效”的解决方案,可能可以使用一些更好的编码标准。希望它可以帮助其他人!

编辑:一些解释。

发现这个github 评论是解决方案的基础。然后找到了这个github pr,他们提到要更改Microsoft.Extensions.Logging.FormattedLogValuesIReadOnlyList<KeyValuePair<string, object>> This can be extract from GetArgument(2)。(来源:https ://stackoverflow.com/a/63701968/1112413 )

  public static class LoggerExtensions
    {
        public static void VerifyLogMustHaveHappened<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            try
            {
                logger.VerifyLog(level, message)
                    .MustHaveHappened();
            }
            catch (Exception e)
            {
                throw new ExpectationException($"while verifying a call to log with message: \"{message}\"", e);
            }
        }

        public static void VerifyLogMustNotHaveHappened<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            try
            {
                logger.VerifyLog(level, message)
                    .MustNotHaveHappened();
            }
            catch (Exception e)
            {
                throw new ExpectationException($"while verifying a call to log with message: \"{message}\"", e);
            }
        }

        public static IVoidArgumentValidationConfiguration VerifyLog<T>(this ILogger<T> logger, LogLevel level, string message)
        {
            return A.CallTo(logger)
                .Where(call => call.Method.Name == "Log" 
                    && call.GetArgument<LogLevel>(0) == level 
                    && CheckLogMessages(call.GetArgument<IReadOnlyList<KeyValuePair<string, object>>>(2), message));
        }

        private static bool CheckLogMessages(IReadOnlyList<KeyValuePair<string, object>> readOnlyLists, string message)
        {
            foreach(var kvp in readOnlyLists)
            {
                if (kvp.Value.ToString().Contains(message))
                {
                    return true;
                }
            }

            return false;
        }
    }
于 2020-10-17T19:03:40.270 回答