问题描述
我正在为自己设计一个有趣的项目,以学习火焰,许多核心概念以及常规知识。
我正在尝试为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.@R_749_4045@ion,"Get Weather Called").MustHaveHappenedOnceExactly();
或A.CallTo(() => _logger.Log(LogLevel.@R_749_4045@ion,"Get Weather Called").MustHaveHappenedOnceExactly();
不一定要有语法糖,但我想让它与FakeItEasy而不是Moq一起使用。
下面是所有涉及的代码。当然,我在第一行Microsoft.Extensions.Logging.ILogger.Log
1 [System.Object] vs
Microsoft.Extensions.Logging.ILogger 1[Server.Controllers.WeatherForecastController].Log
1 [Microsoft.Extensions.Logging.FormattedLogValues]中看到了一些区别,但是我我不知道如何解决此问题,因为我不完全了解出了什么问题。
我的问题显然是,我应该怎么做才能修复它,但同时我也好奇我缺少了什么部分。
我的错误如下:
Assertion Failed for the following call:
Microsoft.Extensions.Logging.ILogger.Log`1[System.Object]
(
logLevel: @R_749_4045@ion,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: @R_749_4045@ion,eventId: 0,state: [[{OriginalFormat},Get Weather Called]],exception: NULL,formatter: System.Func`3[Microsoft.Extensions.Logging.FormattedLogValues,System.Exception,System.String]
)
测试: (使用xUnit,FakeItEasy,Fixture)
LoggerExtensions
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,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,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>>._)
);
}
}
TestConstructor:
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.@R_749_4045@ion,"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.@R_749_4045@ion,"Get Weather Called");
}
}
更新: 找到的选项
仅测试是否以正确的日志级别调用记录器:
public static IVoidArgumentValidationConfiguration VerifyLog<T>(this ILogger<T> logger,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
解决方法
阅读https://stackoverflow.com/users/4728685/pavel-anikhouski对帖子的评论后。我“明白”出了什么问题。进入了无数主题,并提出了一种可能“使用”更好的编码标准的解决方案。希望它对其他人有帮助!
编辑:一些解释。
找到了this的github注释,它是解决方案的基础。然后找到this github pr,他们提到将Microsoft.Extensions.Logging.FormattedLogValues
更改为IReadOnlyList<KeyValuePair<string,object>>
可以从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,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,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;
}
}