这是我的控制器:
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());
}
}
像其他答案建议的那样,传递模拟ILogger很容易,但是验证调用实际上是对logger进行的突然变得更加困难。原因是大多数调用实际上并不属于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%总比没有好:)此外,这种方法将使测试代码
P.S.对于Moq,可以使用为Mock<ILogger<T>>编写扩展方法的方法来验证,以实现类似的结果。
P.P.S.这在。net Core 3中不再工作,查看这个线程了解更多细节:https://github.com/nsubstitute/NSubstitute/issues/597#issuecomment-573742574
@Mahmoud 哈纳菲
我更新了你的答案,以配合当前的状态。
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);
}
}
更新(感谢@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
由于在ILogger.Log中的TState曾经是对象,现在是FormattedLogValues问题中描述的变化,不再工作
幸运的是,stakx提供了一个很好的解决方案。所以我把它贴出来,希望能帮别人节省时间(花了一段时间才弄明白):
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);
使用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传递给服务进行测试。