这是我的控制器:

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());
    }
}

当前回答

我尝试使用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;
            }
        }
    }

您可以轻松地访问所有已记录的消息并断言它提供的所有有意义的参数。

其他回答

使用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传递给服务进行测试。

更新(感谢@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);

只需模拟它以及任何其他依赖:

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。抽象,然后按照示例配置和使用它:

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);
}   

仅仅创建一个虚拟的ILogger对于单元测试来说没有多大价值。您还应该验证是否进行了日志记录调用。您可以使用Moq注入一个模拟ILogger,但验证调用可能有点棘手。本文将深入探讨如何使用Moq进行验证。

下面是文章中一个非常简单的例子:

_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接口确保了这很困难。