这是我的控制器:
public class BlogController : Controller
{
private IDAO<Blog> _blogDAO;
private readonly ILogger<BlogController> _logger;
public BlogController(ILogger<BlogController> logger, IDAO<Blog> blogDAO)
{
this._blogDAO = blogDAO;
this._logger = logger;
}
public IActionResult Index()
{
var blogs = this._blogDAO.GetMany();
this._logger.LogInformation("Index page say hello", new object[0]);
return View(blogs);
}
}
如您所见,我有 2 个依赖项,一个 IDAO
和一个 ILogger
这是我的测试类,我使用 xUnit 进行测试,使用 Moq 创建模拟和存根,我可以轻松模拟 DAO
,但使用 ILogger
我不知道该怎么做,所以我只是传递 null 并注释掉运行测试时调用登录控制器。有没有办法测试但仍然以某种方式保留记录器?
public class BlogControllerTest
{
[Fact]
public void Index_ReturnAViewResult_WithAListOfBlog()
{
var mockRepo = new Mock<IDAO<Blog>>();
mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
var controller = new BlogController(null,mockRepo.Object);
var result = controller.Index();
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Blog>>(viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
}
ILogger
的实际调用时存在一些问题。他在他的博文中提出了一些很好的建议,而我的解决方案似乎可以解决 answer below 中的大部分问题。
只需模拟它以及任何其他依赖项:
var mock = new Mock<ILogger<BlogController>>();
ILogger<BlogController> logger = mock.Object;
//or use this short equivalent
logger = Mock.Of<ILogger<BlogController>>()
var controller = new BlogController(logger);
您可能需要安装 Microsoft.Extensions.Logging.Abstractions
软件包才能使用 ILogger<T>
。
此外,您可以创建一个真正的记录器:
var serviceProvider = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
var factory = serviceProvider.GetService<ILoggerFactory>();
var logger = factory.CreateLogger<BlogController>();
实际上,我发现 Microsoft.Extensions.Logging.Abstractions.NullLogger<>
看起来是一个完美的解决方案。安装包Microsoft.Extensions.Logging.Abstractions
,然后按照示例进行配置和使用:
using Microsoft.Extensions.Logging;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
...
}
using Microsoft.Extensions.Logging;
public class MyClass : IMyClass
{
public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
private readonly ILogger<MyClass> logger;
public MyClass(ILoggerFactory loggerFactory)
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<MyClass>();
}
}
和单元测试
//using Microsoft.VisualStudio.TestTools.UnitTesting;
//using Microsoft.Extensions.Logging;
[TestMethod]
public void SampleTest()
{
ILoggerFactory doesntDoMuch = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
IMyClass testItem = new MyClass(doesntDoMuch);
Assert.IsNotNull(testItem);
}
更新(感谢@Gopal Krishnan 的评论):
对于 Moq >= 4.15.0,以下代码可以正常工作(不再需要演员表):
loggerMock.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => string.Equals("Index page say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Once);
以前版本的答案(对于 Moq < 4.15.0):
对于使用 Moq 的 .net core 3 答案
https://stackoverflow.com/a/54646657/2164198
https://stackoverflow.com/a/54809607/2164198
https://stackoverflow.com/a/56728528/2164198
由于问题 TState in ILogger.Log used to be object, now FormattedLogValues 中描述的更改而不再工作
幸运的是 stakx 提供了一个不错的 workaround。所以我发布它是希望它可以为其他人节省时间(花了一段时间才弄清楚):
loggerMock.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => string.Equals("Index page say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
Times.Once);
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>())
替换为 It.IsAny<Func<It.IsAnyType, Exception, string>>())
,但它不起作用。这是什么魔法:-)?
It.IsAnyType
:_loggerMock.Verify(x => x.Log(LogLevel.Information, It.IsAny<EventId>(), It.Is<object>(o => string.Equals($"Some log message", o.ToString(), StringComparison.InvariantCultureIgnoreCase)), It.IsAny<Exception>(), (Func<object, Exception, string>)It.IsAny<object>()), Times.Once);
使用使用 ITestOutputHelper
(来自 xunit)的自定义记录器来捕获输出和日志。以下是仅将 state
写入输出的小示例。
public class XunitLogger<T> : ILogger<T>, IDisposable
{
private ITestOutputHelper _output;
public XunitLogger(ITestOutputHelper output)
{
_output = output;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_output.WriteLine(state.ToString());
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public void Dispose()
{
}
}
在你的单元测试中使用它,比如
public class BlogControllerTest
{
private XunitLogger<BlogController> _logger;
public BlogControllerTest(ITestOutputHelper output){
_logger = new XunitLogger<BlogController>(output);
}
[Fact]
public void Index_ReturnAViewResult_WithAListOfBlog()
{
var mockRepo = new Mock<IDAO<Blog>>();
mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
var controller = new BlogController(_logger,mockRepo.Object);
// rest
}
}
ILogger
将使其更广泛地可用。 2) BeginScope
不应返回自身,因为这意味着在运行期间开始和结束范围的任何测试方法都将处理记录器。相反,创建一个实现 IDisposable
的私有“虚拟”嵌套类并返回它的一个实例(然后从 XunitLogger
中删除 IDisposable
)。
添加我的 2 美分,这是一个帮助扩展方法,通常放在静态帮助类中:
static class MockHelper
{
public static ISetup<ILogger<T>> MockLog<T>(this Mock<ILogger<T>> logger, LogLevel level)
{
return logger.Setup(x => x.Log(level, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()));
}
private static Expression<Action<ILogger<T>>> Verify<T>(LogLevel level)
{
return x => x.Log(level, 0, It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>());
}
public static void Verify<T>(this Mock<ILogger<T>> mock, LogLevel level, Times times)
{
mock.Verify(Verify<T>(level), times);
}
}
然后,您可以像这样使用它:
//Arrange
var logger = new Mock<ILogger<YourClass>>();
logger.MockLog(LogLevel.Warning)
//Act
//Assert
logger.Verify(LogLevel.Warning, Times.Once());
当然,您可以轻松地扩展它来模拟任何期望(即期望、消息等……)
使用 Moq 4.17.2 更新 .NET 6 此扩展方法还允许使用正则表达式验证消息
static class MockHelper
{
public static void VerifyLog<T>(this Mock<ILogger<T>> logger, LogLevel level, Times times, string? regex = null) =>
logger.Verify(m => m.Log(
level,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((x, y) => regex == null || Regex.IsMatch(x.ToString(), regex)),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
times);
}
这就是如何使用它
logger.VerifyLog(LogLevel.Warning, Times.Exactly(2), "Simple match");
logger.VerifyLog(LogLevel.Warning, Times.Exactly(2), "[Yy]ou\scould do regex too.*");
ILogger
的版本:gist.github.com/timabell/d71ae82c6f3eaa5df26b147f9d3842eb
It.Is<string>(s => s.Equals("A parameter is empty!"))
如果一个仍然是实际的。在 .net core >= 3 的测试中记录输出的简单方法
[Fact]
public void SomeTest()
{
using var logFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = logFactory.CreateLogger<AccountController>();
var controller = new SomeController(logger);
var result = controller.SomeActionAsync(new Dto{ ... }).GetAwaiter().GetResult();
}
在@ivan-samygin 和@stakx 的工作基础上更进一步,这里有一些扩展方法也可以匹配异常和所有日志值(KeyValuePairs)。
这些工作(在我的机器上;))与 .Net Core 3、Moq 4.13.0 和 Microsoft.Extensions.Logging.Abstractions 3.1.0 一起工作。
/// <summary>
/// Verifies that a Log call has been made, with the given LogLevel, Message and optional KeyValuePairs.
/// </summary>
/// <typeparam name="T">Type of the class for the logger.</typeparam>
/// <param name="loggerMock">The mocked logger class.</param>
/// <param name="expectedLogLevel">The LogLevel to verify.</param>
/// <param name="expectedMessage">The Message to verify.</param>
/// <param name="expectedValues">Zero or more KeyValuePairs to verify.</param>
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel expectedLogLevel, string expectedMessage, params KeyValuePair<string, object>[] expectedValues)
{
loggerMock.Verify(mock => mock.Log(
expectedLogLevel,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => MatchesLogValues(o, expectedMessage, expectedValues)),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()
)
);
}
/// <summary>
/// Verifies that a Log call has been made, with LogLevel.Error, Message, given Exception and optional KeyValuePairs.
/// </summary>
/// <typeparam name="T">Type of the class for the logger.</typeparam>
/// <param name="loggerMock">The mocked logger class.</param>
/// <param name="expectedMessage">The Message to verify.</param>
/// <param name="expectedException">The Exception to verify.</param>
/// <param name="expectedValues">Zero or more KeyValuePairs to verify.</param>
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, string expectedMessage, Exception expectedException, params KeyValuePair<string, object>[] expectedValues)
{
loggerMock.Verify(logger => logger.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => MatchesLogValues(o, expectedMessage, expectedValues)),
It.Is<Exception>(e => e == expectedException),
It.Is<Func<It.IsAnyType, Exception, string>>((o, t) => true)
));
}
private static bool MatchesLogValues(object state, string expectedMessage, params KeyValuePair<string, object>[] expectedValues)
{
const string messageKeyName = "{OriginalFormat}";
var loggedValues = (IReadOnlyList<KeyValuePair<string, object>>)state;
return loggedValues.Any(loggedValue => loggedValue.Key == messageKeyName && loggedValue.Value.ToString() == expectedMessage) &&
expectedValues.All(expectedValue => loggedValues.Any(loggedValue => loggedValue.Key == expectedValue.Key && loggedValue.Value == expectedValue.Value));
}
已经提到您可以将其模拟为任何其他界面。
var logger = new Mock<ILogger<QueuedHostedService>>();
到目前为止,一切都很好。
好消息是您可以使用 Moq
来验证是否已执行某些调用。例如,在这里我检查日志是否已使用特定的 Exception
调用。
logger.Verify(m => m.Log(It.Is<LogLevel>(l => l == LogLevel.Information), 0,
It.IsAny<object>(), It.IsAny<TaskCanceledException>(), It.IsAny<Func<object, Exception, string>>()));
使用 Verify
时,重点是针对来自 ILooger
接口的真正 Log
方法,而不是扩展方法。
正如其他答案所建议的那样,通过模拟 ILogger
很容易,但是验证是否确实对记录器进行了调用突然变得更加困难。原因是大多数调用实际上并不属于 ILogger
接口本身。
所以调用最多的是扩展方法,它们调用接口的唯一 Log
方法。原因似乎是,如果您只有一个而不是很多归结为相同方法的重载,则实现接口会更容易。
当然,缺点是验证一个呼叫是否已经发出突然变得更加困难,因为您应该验证的呼叫与您所做的呼叫非常不同。有一些不同的方法可以解决这个问题,我发现用于模拟框架的自定义扩展方法将使它最容易编写。
以下是我使用 NSubstitute
制作的方法示例:
public static class LoggerTestingExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log(
LogLevel.Error,
0,
Arg.Is<FormattedLogValues>(v => v.ToString() == message),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
}
}
这就是它的使用方式:
_logger.Received(1).LogError("Something bad happened");
看起来就像您直接使用该方法一样,这里的技巧是我们的扩展方法获得优先权,因为它在命名空间中比原始方法“更接近”,因此将使用它。
不幸的是,它没有提供我们想要的 100%,即错误消息不会那么好,因为我们不直接检查字符串,而是检查涉及字符串的 lambda,但 95% 总比没有好:) 另外这种方法将使测试代码
PS 对于 Moq,可以使用为执行 Verify
的 Mock<ILogger<T>>
编写扩展方法的方法来实现类似的结果。
PPS 这在 .Net Core 3 中不再起作用,请查看此线程以获取更多详细信息:https://github.com/nsubstitute/NSubstitute/issues/597#issuecomment-573742574
当使用 StructureMap / Lamar 时:
var c = new Container(_ =>
{
_.For(typeof(ILogger<>)).Use(typeof(NullLogger<>));
});
文件:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger?view=aspnetcore-2.1
http://structuremap.github.io/generics/
仅仅创建一个虚拟 ILogger
对于单元测试不是很有价值。您还应该验证是否进行了日志记录调用。您可以使用 Moq 注入模拟 ILogger
,但验证调用可能有点棘手。 This article 深入探讨了使用起订量进行验证。
这是文章中的一个非常简单的示例:
_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));
它验证是否记录了信息消息。但是,如果我们想要验证有关消息的更复杂的信息,例如消息模板和命名属性,则变得更加棘手:
_loggerMock.Verify
(
l => l.Log
(
//Check the severity level
LogLevel.Error,
//This may or may not be relevant to your scenario
It.IsAny<EventId>(),
//This is the magical Moq code that exposes internal log processing from the extension methods
It.Is<It.IsAnyType>((state, t) =>
//This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
//Note: messages should be retrieved from a service that will probably store the strings in a resource file
CheckValue(state, LogTest.ErrorMessage, "{OriginalFormat}") &&
//This confirms that an argument with a key of "recordId" was sent with the correct value
//In Application Insights, this will turn up in Custom Dimensions
CheckValue(state, recordId, nameof(recordId))
),
//Confirm the exception type
It.IsAny<NotImplementedException>(),
//Accept any valid Func here. The Func is specified by the extension methods
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
//Make sure the message was logged the correct number of times
Times.Exactly(1)
);
我确信您可以对其他模拟框架执行相同的操作,但 ILogger
界面确保它很困难。
我尝试使用 NSubstitute 来模拟 Logger 接口(但失败了,因为 Arg.Any<T>()
需要一个我无法提供的类型参数),但最终以以下方式创建了一个测试记录器(类似于@jehof 的答案):
internal sealed class TestLogger<T> : ILogger<T>, IDisposable
{
private readonly List<LoggedMessage> _messages = new List<LoggedMessage>();
public IReadOnlyList<LoggedMessage> Messages => _messages;
public void Dispose()
{
}
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var message = formatter(state, exception);
_messages.Add(new LoggedMessage(logLevel, eventId, exception, message));
}
public sealed class LoggedMessage
{
public LogLevel LogLevel { get; }
public EventId EventId { get; }
public Exception Exception { get; }
public string Message { get; }
public LoggedMessage(LogLevel logLevel, EventId eventId, Exception exception, string message)
{
LogLevel = logLevel;
EventId = eventId;
Exception = exception;
Message = message;
}
}
}
您可以轻松访问所有记录的消息并断言随它提供的所有有意义的参数。
我创建了一个包 Moq.ILogger,以使测试 ILogger 扩展更加容易。
您实际上可以使用以下更接近您的实际代码的内容。
loggerMock.VerifyLog(c => c.LogInformation(
"Index page say hello",
It.IsAny<object[]>());
不仅更容易编写新的测试,而且维护也是免费的。
可以在 here 中找到 repo,并且还有一个 nuget 包 (Install-Package ILogger.Moq
)。
我还在我的 blog 上用一个真实的例子来解释它。
简而言之,假设您有以下代码:
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);
}
}
然后你可以写一些测试,比如
[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 {0} outstanding orders was completed", 100));
}
[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_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"));
}
最简单的解决方案是使用 NullLogger
。它是 Microsoft.Extensions.Logging.Abstractions
的一部分。
无需弄乱工厂和其他不必要的建筑。只需添加:
ILogger<BlogController> logger = new NullLogger<BlogController>();
@Mahmoud Hanafy
我更新了您的答案以适应当前状态。
static class MockLogHelper
{
public static ISetup<ILogger<T>> MockLog<T>(this Mock<ILogger<T>> logger, LogLevel level)
{
return logger.Setup(x => x.Log(level, It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()));
//return logger.Setup(x => x.Log(level, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()));
}
private static Expression<Action<ILogger<T>>> Verify<T>(LogLevel level)
{
return x => x.Log(level, 0, It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>());
//return x => x.Log(level, 0, It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>());
}
public static void Verify<T>(this Mock<ILogger<T>> mock, LogLevel level, Times times)
{
mock.Verify(Verify<T>(level), times);
}
}
使用 Telerik Just Mock 创建记录器的模拟实例:
using Telerik.JustMock;
...
context = new XDbContext(Mock.Create<ILogger<XDbContext>>());
使用 NullLogger - 什么都不做的简约记录器。
public interface ILoggingClass
{
public void LogCritical(Exception exception);
}
public class LoggingClass : ILoggingClass
{
private readonly ILogger<LoggingClass> logger;
public LoggingClass(ILogger<LoggingClass> logger) =>
this.logger = logger;
public void LogCritical(Exception exception) =>
this.logger.LogCritical(exception, exception.Message);
}
并在测试方法使用中,
ILogger<LoggingClass> logger = new NullLogger<LoggingClass>();
LoggingClass loggingClass = new LoggingClass(logger);
并将 loggingClass 传递给服务进行测试。
AddLogging
中调用AddDebug
,而不是从ILoggerFactory
。new ServiceCollection().AddLogging(builder => builder.AddDebug())...