模拟基地 class 受保护 属性

Mocking base class protected property

我目前正在创建一个 Mock 单元测试来测试 LogManager Logger.Error() 调用 class 方法,如下所示。

public class Test : BaseClass
{
   public override void DoSomething(object value)
   {
      try
      { 
         if (value == null)
         {
             throw new Exception();
         }         
      }
      catch(exception ex)
      {
         Logger.Error("Exception raised!", ex);
      }
   }
}

我的 class 使用抽象基础 class,它有一个 属性 定义如下 ILog

public abstract class BaseClass
{
   protected ILog Logger => LogManager.GetLogger(GetType());
}

问题是如何模拟这个基数 class 属性 Logger 以便我可以测试 Logger.Error() 是否被引发?

这是我的单元测试

[Test]
public void PassingNullParamShouldRaiseError()
{
    // Arrange
    var mockLog = new Mock<ILog>();
    var sut = new Test();

    // Action
    sut.DoSomething(null);

    // Assert
    mockLog.Verify(x => x.Error("Exception raised!"), Times.Once());
}

如果你使基础属性虚拟

public abstract class BaseClass {
   protected virtual ILog Logger => LogManager.GetLogger(GetType());
}

您现在可以使用可在测试时使用的模拟覆盖 属性

[Test]
public void PassingNullParamShouldRaiseError() {
    // Arrange
    var mockLog = new Mock<ILog>();

    var sut = new Mock<Test>() {
        CallBase = true //Important to be able to call base member
    };

    //override default behavior of protected logger
    sut.Protected()
        .Setup<ILog>("Logger")
        .Returns(mockLog.Object);

    // Act
    sut.Object.DoSomething(null);

    // Assert
    mockLog.Verify(x => x.Error("Exception raised!", It.IsAny<Exception>()), Times.Once());
}

现在理想情况下,您的基础 class 不应像目前使用 LogManager

那样与静态实现问题紧密耦合

记录器应该显式地注入依赖 class。或者至少通过日志工厂抽象,

public interface ILoggerFactory {
    ILog GetLogger(Type type);
}

谁的实现可以包装那种代码味道

public class DefaultLoggerFactory : ILoggerFactory {
    public ILog GetLogger(Type type) => LogManager.GetLogger(type);
}

可以用来获取想要的日志

public class Test  {
    private readonly ILog logger;

    public Test(ILoggerFactory logs)  {
        logger = logs.GetLogger(GetType());
    }

    public void DoSomething(object value) {
        try {
            if (value == null) {
                throw new Exception();
            }
        } catch (Exception ex) {
            logger.Error("Exception raised!", ex);
        }
    }
}