You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Ninject MockingKernel模拟ILog接口时遇到的异常问题

如何用Ninject.MockingKernel Mock BaseController中直接创建的ILog实例?

我正在给遗留ASP.NET代码添加单元测试,使用Moq和Ninject.MockingKernel。现有代码结构如下:

BaseController类:

public class BaseController : Controller {
    private IAuthenticationManager authenticationManager;
    public ILog log;
    public BaseController() {
        var kernel = new StandardKernel();
        kernel.Load(Assembly.GetExecutingAssembly());
        log = kernel.Get<ILog>();
    }
}

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

我的需求是Mock BaseController中通过StandardKernel获取的ILog实例(该实例被其他控制器继承且未采用注入方式传入),希望测试时让BaseController拿到Mock的ILog对象,类似Mock DatabaseContext的场景。

参考文档后我尝试了以下代码:

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。查阅资料得知该异常通常由构造函数含参数导致,但我的Log类是无参构造。请问我的处理方式是否正确?若正确,问题出在哪里?


问题分析

你的核心问题有两个:

  1. 容器实例不共享:BaseController的构造函数里自己创建了一个全新的StandardKernel,和测试代码中的MoqMockingKernel是完全独立的两个容器。不管你在测试的MockingKernel里怎么配置,BaseController都会从它自己创建的StandardKernel中获取真实的Log实例,根本不会用到你Mock的对象。
  2. Mock绑定冲突:你调用mockingKernal.Bind<ILog>().To<Logging.Log>()把ILog绑定到了真实的Log类,之后再调用GetMock<ILog>()就会触发异常——因为MoqMockingKernel默认会为接口自动生成Mock实例,但你已经手动绑定到了具体实现,此时GetMock无法从真实实例生成Mock对象,所以抛出"Object instance was not created by Moq"的错误。

解决方案

方案1:改造BaseController(推荐,长期更合理)

虽然是遗留代码,但让BaseController支持依赖注入会让测试变得简单且可维护。可以修改构造函数,保留原有无参构造以兼容现有代码,同时新增带参数的构造函数供测试使用:

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

    // 保留无参构造,兼容现有业务代码
    public BaseController() : this(new StandardKernel()) {
    }

    // 供测试使用的构造函数,接受自定义Kernel
    public BaseController(IKernel kernel) {
        kernel.Load(Assembly.GetExecutingAssembly());
        log = kernel.Get<ILog>();
    }

    // 更优的方式:直接注入ILog(符合依赖注入原则)
    // public BaseController(ILog logInstance) {
    //     log = logInstance;
    // }
}

测试代码修改为:

// 初始化MockingKernel并创建ILog的Mock
var mockingKernel = new MoqMockingKernel();
var logMock = mockingKernel.GetMock<ILog>();
logMock.Setup(x => x.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()));

// 使用带参数的构造函数创建BaseController实例
var controller = new BaseController(mockingKernel);
// 此时controller.log就是你配置的Mock实例

方案2:不修改BaseController的临时方案

如果暂时不能改动BaseController的构造函数,可以通过反射替换它的log字段来注入Mock实例:

// 创建ILog的Mock并配置
var logMock = new Mock<ILog>();
logMock.Setup(x => x.Create(It.IsAny<AuditAction>(), It.IsAny<long>(), It.IsAny<string>()));

// 创建BaseController实例
var controller = new BaseController();

// 用反射替换public的log字段
typeof(BaseController)
    .GetField("log", BindingFlags.Public | BindingFlags.Instance)
    .SetValue(controller, logMock.Object);

这个方案属于“ hack 式”的临时解决办法,破坏了类的封装性,一旦BaseController后续修改了log字段的访问修饰符或名称,测试就会失效,所以仅推荐作为短期过渡方案。

内容的提问来源于stack exchange,提问作者jmo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:51:58