【问题标题】:NSubstitute ILogger .NET CoreNSubstitute ILogger .NET Core
【发布时间】:2018-03-13 18:01:28
【问题描述】:

我正在尝试围绕我的异常处理编写单元测试,以便我可以验证我的记录器是否正确记录了异常。我使用 NSubstitute 作为模拟框架,Microsoft.Extensions.Logging.ILogger 我必须关注我的测试:

[Fact]
public void LogsExcpetionWhenErrorOccursInCreate()
{
   var newUser = new UserDataModel
   {
      FirstName = "Rick",
      MiddleName = "Jason",
      LastName = "Grimes",
      Email = "rick.grimes@thedead.com",
      Created = new DateTime(2007, 8, 15)
   };
   var exception = new Exception("Test Exception");
   // configure InsertOne to throw a generic excpetion
   _mongoContext.InsertOne(newUser).Returns(x => { throw exception; });

   try
   {
      _collection.Create(newUser);
   }
   catch
   {
      // validate that the logger logs the exception as an error
      _logger.Received().LogError(exception.Message);
   }
}

用以下方法测试日志记录:

public UserDataModel Create(UserDataModel user)
{
     try
     {
          return MongoContext.InsertOne(user);                
     }
     catch(Exception e)
     {
           _logger?.LogError(e.Message);
           throw new DataAccessException("An error occurred while attempting to create a user.", e);
      }

}

我的测试失败并出现以下错误:

Message: NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
    Log<Object>(Error, 0, Test Exception, <null>, Func<Object, Exception, String>)
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
    Log<Object>(Error, 0, *Test Exception*, <null>, Func<Object, Exception, String>)

我不确定为什么会失败,因为即使在错误消息中,调用也是相同的。

提前致谢!

更新:

这是测试的构造函数,这是我注入记录器模拟的地方:

public UserCollectionTest()
{
   _mongoContext = Substitute.For<IMongoContext<UserDataModel>>();
   _logger = Substitute.For<ILogger>();
   // create UserCollection with our mock client
   _collection = new UserCollection(_mongoContext, _logger);
}

【问题讨论】:

  • 请说明您如何创建和注入 ILogger 替代品。
  • 我已经用测试的构造函数更新了帖子,这是我注入子的地方。

标签: c# .net unit-testing .net-core nsubstitute


【解决方案1】:

LogError 不是 ILogger 方法,因此当您尝试检查是否使用某些参数调用此方法时,NSubstitute 会尝试以某种方式处理它(我不知道具体如何)并失败。

LogError 扩展方法的代码为:

public static void LogError(this ILogger logger, string message, params object[] args)
{
  if (logger == null)
    throw new ArgumentNullException("logger");
  logger.Log<object>(LogLevel.Error, (EventId) 0, (object) new FormattedLogValues(message, args), (Exception) null, LoggerExtensions._messageFormatter);
}

所以你必须检查 Log 方法是否被调用。

我稍微简化了你的例子。我觉得思路应该很清楚。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var logger = Substitute.For<ILogger>();
        try
        {
            Create(logger);
        }
        catch
        {
            logger.CheckErrorMessage("My Message");
        }
    }

    public string Create(ILogger logger)
    {
        try
        {
            throw new Exception("My Message");
        }
        catch (Exception e)
        {
            logger?.LogError(e.Message);
            throw new Exception("An error occurred while attempting to create a user.", e);
        }
    }
}

public static class TestExtensions
{
    public static void CheckErrorMessage(this ILogger logger, string message)
    {
        logger.Received().Log(
            LogLevel.Error,
            Arg.Any<EventId>(),
            Arg.Is<object>(o => o.ToString() == message),
            null,
            Arg.Any<Func<object, Exception, string>>());
    }
}

【讨论】:

  • 对我不起作用。它根本不匹配。
  • 什么不匹配。也许是因为 NSubstitute 的更新版本。能不能说的具体点?
  • @AndreSoares 看起来这个解决方案在发布答案时不存在的较新版本的 .NET 中不再适用。 github.com/nsubstitute/NSubstitute/issues/597
【解决方案2】:

看起来接受的答案在 .NET Core 3 或 .NET 5 中不起作用。

这是在github issue 中找到的解决方法

创建一个新的 MockLogger 类

public abstract class MockLogger : ILogger
{
    void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) => 
        Log(logLevel, formatter(state, exception));

    public abstract void Log(LogLevel logLevel, string message);

    public virtual bool IsEnabled(LogLevel logLevel) => true;

    public abstract IDisposable BeginScope<TState>(TState state);
}

用新的模拟记录器替换你的模拟记录器

// Old 
// var logger = Substitute.For<ILogger>();
// New
var logger = Substitute.For<MockLogger>();

使用新的记录器来检查调用

logger.Received().Log(LogLevel.Error, Arg.Is<string>(s => s.Contains("some log message")));

要与通用 ILogger&lt;T&gt; 一起使用,只需将类更改为

public abstract class MockLogger<T> : ILogger<T>

【讨论】:

    猜你喜欢
    • 2020-04-21
    • 2021-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-20
    • 2020-06-26
    相关资源
    最近更新 更多