使用 Ninject 模拟 StandardKernal 接口

Mocking StandardKernal Interface with Ninject

我正在使用最小起订量和 Ninject.MockingKernal.

向一些遗留 ASP 代码添加单元测试
    public class BaseController : Controller
    {
        private IAuthenticationManager authenticationManager;
        public ILog log;

        public BaseController()
        {
            var kernel = new StandardKernel();
            kernel.Load(Assembly.GetExecutingAssembly());
            log = kernel.Get<ILog>();
        }
    }

日志界面:

public interface ILog
    {
        AuditTrail Create(AuditAction action, string description = null);
        AuditTrail Create(AuditAction action, long reservationId, string description = null);
        AuditTrail Create(IUser user, AuditAction action);
        AuditTrail Create(IUser user, AuditAction action, string description = null);
        AuditTrail Create(IUser user, AuditAction action, long reservationId, string description = null);
    }

我正在尝试模拟从内核设置的日志实例。这个日志被其他controller继承,没有被注入。我希望能够在请求时 return 模拟对象,就像我在其他情况下所做的那样(例如 return 从工厂中获取模拟 DatabaseContext)。

我看过这个How to do Setup of mocks with Ninject's MockingKernel (moq) and the GitHub example: https://github.com/ninject/Ninject.MockingKernel/wiki/Examples,还有很多其他的。

根据我收集到的信息,我需要按照以下思路做一些事情:

mockingKernal = new MoqMockingKernel();
mockingKernal.Bind<ILog>().To<Logging.Log>();
var foo = mockingKernal.GetMock<ILog>();
foo.Setup(x => x.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()));

但是,如果我 运行 这个,我得到一个错误 System.ArgumentException: Object instance was not created by Moq. 从我在网上找到的,这是由 class 在构造函数中有一个参数引起的,但是在这种情况下,日志 class 没有。

我的处理方式是否正确?如果我是,我做错了什么?任何帮助将不胜感激

上面的 approach/design 会导致各种头痛 maintain/test 因为控制器与内核(IoC 容器)紧密耦合,这基本上不允许一个人能够很容易 mock/replace 它进行测试。

另请注意,所链接的示例都有一个共同点,即能够将依赖项显式注入到被测对象中。

以上基本上是使用内核作为服务定位器。

尝试在该代码上涂抹口红可能会改变其外观,但对气味没有任何影响。

理想情况下,设计应遵循明确的 dependency principle

Methods and classes should explicitly require (typically through method parameters or constructor parameters) any collaborating objects they need in order to function correctly.

public class BaseController : Controller {
    private IAuthenticationManager authenticationManager;
    public ILog log;

    public BaseController(ILog log, IAuthenticationManager authenticationManager) {
        this.log = log;
        this.authenticationManager = authenticationManager;
    }
}

这将允许依赖项成为 mocked/faked/stubbed 并注入到它们的依赖项中。

//Arrange
var logger = new Mock<ILog>();
logger
    .Setup(_ => _.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()))
    .Return(new AuditTrail);

var controller = new BaseController(logger.Object, null);

//Act

//...

不应直接在 类 中调用容器,而应在组合根目录中配置它。

我建议检查当前设计并相应地进行重构。