ore*_*bac 27 c# logging mocking
我有一个接收 ILogger 的类,我想模拟 LogInformation 调用,但这是一个扩展方法。我如何为此进行适当的设置调用?
crg*_*den 27
如果您使用的是 Moq >= 4.13,这里是一种模拟方法ILogger
:
logger.Verify(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()));
Run Code Online (Sandbox Code Playgroud)
你可以改变的It.IsAny<LogLevel>()
,It.IsAny<EventId>()
和It.IsAny<Exception>()
存根更具体,但使用It.IsAnyType
是必要的,因为FormattedLogValues
现在internal
。
参考:ILogger.Log 中的 TState 以前是对象,现在是 FormattedLogValues
liv*_*ve2 16
带有回调的示例,使用 Moq 4.14.5 进行测试。有关此Github 问题的更多信息
var logger = new Mock<ILogger<ReplaceWithYourObject>>();
logger.Setup(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()))
.Callback(new InvocationAction(invocation =>
{
var logLevel = (LogLevel)invocation.Arguments[0]; // The first two will always be whatever is specified in the setup above
var eventId = (EventId)invocation.Arguments[1]; // so I'm not sure you would ever want to actually use them
var state = invocation.Arguments[2];
var exception = (Exception?)invocation.Arguments[3];
var formatter = invocation.Arguments[4];
var invokeMethod = formatter.GetType().GetMethod("Invoke");
var logMessage = (string)invokeMethod?.Invoke(formatter, new[] { state, exception });
}));
Run Code Online (Sandbox Code Playgroud)
一个完整的 UnitTesting 通用辅助类
public static class LoggerHelper
{
public static Mock<ILogger<T>> GetLogger<T>()
{
var logger = new Mock<ILogger<T>>();
logger.Setup(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()))
.Callback(new InvocationAction(invocation =>
{
var logLevel = (LogLevel)invocation.Arguments[0]; // The first two will always be whatever is specified in the setup above
var eventId = (EventId)invocation.Arguments[1]; // so I'm not sure you would ever want to actually use them
var state = invocation.Arguments[2];
var exception = (Exception?)invocation.Arguments[3];
var formatter = invocation.Arguments[4];
var invokeMethod = formatter.GetType().GetMethod("Invoke");
var logMessage = (string)invokeMethod?.Invoke(formatter, new[] { state, exception });
Trace.WriteLine(logMessage);
}));
return logger;
}
}
Run Code Online (Sandbox Code Playgroud)
Sco*_*nen 12
在这种情况下,“假”课程可能比起订量更容易。创建起来需要多一点工作,但是您可以永远重复使用它,并且它比 Moq 回调更容易阅读和使用。(我喜欢 Moq,但当有更简单的方法时就不喜欢。)
对于大多数用例,这将按原样工作,或者您可以对其进行调整。
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
public class FakeLogger<T> : ILogger, ILogger<T>
{
public List<LogEntry> LogEntries { get; } = new List<LogEntry>();
public IEnumerable<LogEntry> InformationEntries =>
LogEntries.Where(e => e.LogLevel == LogLevel.Information);
public IEnumerable<LogEntry> WarningEntries =>
LogEntries.Where(e => e.LogLevel == LogLevel.Warning);
public IEnumerable<LogEntry> ErrorEntries =>
LogEntries.Where(e => e.LogLevel == LogLevel.Error);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
LogEntries.Add(new LogEntry(logLevel, eventId, state, exception));
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return new LoggingScope();
}
public class LoggingScope : IDisposable
{
public void Dispose()
{
}
}
}
public class LogEntry
{
public LogEntry(LogLevel logLevel, EventId eventId, object state, Exception exception)
{
LogLevel = logLevel;
EventId = eventId;
State = state;
Exception = exception;
}
public LogLevel LogLevel { get; }
public EventId EventId { get; }
public object State { get; }
public Exception Exception { get; }
public string Message => State?.ToString() ?? string.Empty;
public Dictionary<string, object> Params => (State as IReadOnlyList<KeyValuePair<string, object?>>)!.ToDictionary(key => key.Key, value => value.Value)!;
public string FormatString => (string)Params["{OriginalFormat}"];
}
Run Code Online (Sandbox Code Playgroud)
创建一个实例并将其作为记录器注入到您的测试类中。然后您可以查看LogEntries
集合中的对象以了解记录的内容。
的类型State
通常为FormattedLogValues
,但您可以调用State.ToString()
并仅获取字符串值。
如果你有一个logger
( new Mock<ILogger<ReplaceWithYourObject>>()
) 模拟注入到 ctor 中。那么这应该有助于验证日志消息和日志级别。
logger.Verify(x => x.Log(
LogLevel.Information, //Change LogLevel as required
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((object v, Type _) =>
v.ToString().Contains("MessageToVerify")), // Change MessageToVerify as required
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()));
Run Code Online (Sandbox Code Playgroud)
ILogger 通常通过扩展方法、LogWarning、LogError 等使用。
就我而言,我对 LogWarning 方法感兴趣,在查看代码后,该方法从 ILogger 调用 Log 方法。为了用 Moq 模拟它,这就是我最终做的:
var list = new List<string>();
var logger = new Mock<ILogger>();
logger
.Setup(l => l.Log<FormattedLogValues>(LogLevel.Warning, It.IsAny<EventId>(), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<FormattedLogValues, Exception, string>>()))
.Callback(
delegate (LogLevel logLevel, EventId eventId, FormattedLogValues state, Exception exception, Func<FormattedLogValues, Exception, string> formatter)
{
list.Add(state.ToString());
});
Run Code Online (Sandbox Code Playgroud)
在较新版本的 .NET Core 3.0 中,这不起作用。因为 FormattedLogValues 是内部类型。您需要将最小起订量版本更新为至少:
`<PackageReference Include="Moq" Version="4.16.0" />`
Run Code Online (Sandbox Code Playgroud)
更新后Moq
解决方法是这样的:
var log = new List<string>();
var mockLogger = new Mock<ILogger>();
mockLogger.Setup(
l => l.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()))
.Callback((IInvocation invocation) =>
{
var logLevel = (LogLevel)invocation.Arguments[0];
var eventId = (EventId)invocation.Arguments[1];
var state = (IReadOnlyCollection<KeyValuePair<string, object>>)invocation.Arguments[2];
var exception = invocation.Arguments[3] as Exception;
var formatter = invocation.Arguments[4] as Delegate;
var formatterStr = formatter.DynamicInvoke(state, exception);
log.Add(
$"{logLevel} - {eventId.Id} - Testing - {formatterStr}");
});
Run Code Online (Sandbox Code Playgroud)
注意特殊的 cast:(Func<It.IsAnyType, Exception, string>)It.IsAny<object>())
以及处理参数的 IInvocation 。
这就是我对Moq (v4.10.1) 框架的解决方法。
public static class TestHelper
{
public static Mock<ILogger<T>> GetMockedLoggerWithAutoSetup<T>()
{
var logger = new Mock<ILogger<T>>();
logger.Setup<object>(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()));
return logger;
}
public static void VerifyLogMessage<T>(Mock<ILogger<T>> mockedLogger, LogLevel logLevel, Func<string, bool> predicate, Func<Times> times)
{
mockedLogger.Verify(x => x.Log(logLevel, 0, It.Is<object>(p => predicate(p.ToString())), null, It.IsAny<Func<object, Exception, string>>()), times);
}
}
Run Code Online (Sandbox Code Playgroud)
——
public class Dummy
{
}
[Fact]
public void Should_Mock_Logger()
{
var logger = TestHelper.GetMockedLoggerWithAutoSetup<Dummy>();
logger.Object.LogInformation("test");
TestHelper.VerifyLogMessage<Dummy>(logger, LogLevel.Information, msg => msg == "test", Times.Once);
}
Run Code Online (Sandbox Code Playgroud)
——
事情是,
如果我选择了其他<TCustom>
比<object>
对logger.Setup()
,它会失败上Verify
步说,0话费被用于制作x.Log<TCustom>
和展示给打了一个电话x.Log<object>
。所以我将我的通用记录器设置为模拟Log<object>(..)
方法。
归档时间: |
|
查看次数: |
11358 次 |
最近记录: |