这是我的控制器:
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);
}
}
正如你所看到的,我有两个依赖项,一个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());
}
}
前面已经提到过,您可以将其模拟为任何其他接口。
var logger = new Mock<ILogger<QueuedHostedService>>();
到目前为止一切顺利。
好的一点是,您可以使用Moq来验证某些调用是否已经执行。例如,在这里,我检查日志已经被一个特定的异常调用。
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方法,而不是扩展方法进行验证。
使用一个使用ITestOutputHelper(来自xunit)的自定义记录器来捕获输出和日志。下面是一个只将状态写入输出的小示例。
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
}
}
使用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传递给服务进行测试。
实际上,我找到了Microsoft.Extensions.Logging.Abstractions。NullLogger<>,这看起来像是一个完美的解决方案。安装包Microsoft.Extensions.Logging。抽象,然后按照示例配置和使用它:
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);
}
我尝试使用NSubstitute模拟记录器接口(并且失败了,因为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;
}
}
}
您可以轻松地访问所有已记录的消息并断言它提供的所有有意义的参数。