当构造函数具有使用 Func<T> 的依赖项时,Moq CreateInstance 失败

Moq CreateInstance fails when constructor has dependencies using Func<T>

public class MyService
{
    private readonly ISomething _something;
    private readonly Func<IRarelyGetUsed> _rarelyGetUsed;

    public MyService(ISomething something, Func<IRarelyGetUsed> rarelyGetUsed)
    {
        _something = something;
        _rarelyGetUsed = rarelyGetUsed;
    }
}

我们将 Autofac 用于 IOC 并发现使用 Func<T> 方法可以获得巨大的性能提升(在负载下时),因为这些依赖关系在使用之前不会得到解决,并且在某些情况下某些情况下不使用依赖项。

我们也使用 Moq 进行一些单元测试。

var _container = new AutoMocker();
var _service = _container.CreateInstance<MyService>();

此时爆炸了-System.NullReferenceException : Object reference not set to an instance of an object.

有人知道如何告诉 Moq 与 Func 依赖项一起玩吗?

请注意,如果我将 Func<IRarelyGetUsed> 更改为 IRarelyGetUsed,也不例外。

编辑: 原来 nuget 包很旧 - 更新包后 https://github.com/tkellogg/Moq.AutoMocker 现在可以使用了。

但是,还有一个问题需要解决 -

_container.GetMock<Func<IRarelyGetUsed>>().Setup(p => p().DoSomething(It.IsAny<string>())).Returns(true).Verifiable();

尝试设置上述方法的结果 - Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.InvocationExpression'

编辑 2:

var serviceMock = _container.GetMock<IRarelyGetUsed>();
serviceMock.Setup(r => r.DoSomething()).Returns(someData);
_container.GetMock<Func<IRarelyGetUsed>>().Setup(s => s()).Returns(serviceMock.Object);

上面的现在可以工作了,但是它需要设置 Func<IRarelyGetUsed>IRarelyGetUsed - 如果只需要做一个就好了,否则每次测试的开销会更多。

您是否尝试过使用 Lazy<T> 而不是 Func<T> 来实现您想要的延迟加载?与 Moq 一起使用可能比 Func 更好。

Documentation on Lazy

您可以使用 AutoMocker 为每个 T 自动连接一个 Func<T>

public void RegisterFuncs(AutoMocker autoMocker, IEnumerable<Type> types)
{
    var use = typeof(AutoMocker).GetMethods()
        .First(t => t.Name == "Use" && 
                    t.GetGenericArguments().First().Name == "TService");
    var get = typeof(AutoMocker).GetMethod("Get");
    foreach (var type in types)
    {
        // _.container.Use<Func<T>>()
        var typedUse = use.MakeGenericMethod(typeof(Func<>).MakeGenericType(type));

        // _container.Get<T>()
        var typedGet = get.MakeGenericMethod(type);
        var target = Expression.Constant(autoMocker);
        var call = Expression.Call(target, typedGet);

        // () => _container.Get<T>()
        var lambda = Expression.Lambda(call);

        // _.container.Use<Func<T>>(() => _container.Get<T>())
        typedUse.Invoke(autoMocker, new object[] { lambda.Compile() });
    }
}

// Then call with your AutoMocker instance and the interfaces you want to wire up
var types = typeof(SomeNamespace.ISomeInterface).Assembly.GetExportedTypes()
    .Where(t => t.IsInterface && !t.ContainsGenericParameters);
RegisterFuncs(yourAutoMocker, types);

运行 在创建容器后的测试设置中。

注意:要使上述工作适用于 Lazy<T>,您必须使用 Func<T> 实例化 Lazy<T>,因此您需要如下内容:

public void RegisterLazys(AutoMocker autoMocker, IEnumerable<Type> types)
{
    var use = typeof(AutoMocker).GetMethods()
        .First(t => t.Name == "Use" && 
                    t.GetGenericArguments().First().Name == "TService");
    var get = typeof(AutoMocker).GetMethod("Get");
    foreach (var type in types)
    {
        // Lazy<T>
        var lazyT = typeof(Lazy<>).MakeGenericType(type);

        // _.container.Use<Lazy<T>>()
        var typedUse = use.MakeGenericMethod(lazyT);

        // _container.Get<T>()
        var typedGet = get.MakeGenericMethod(type);
        var target = Expression.Constant(autoMocker);
        var call = Expression.Call(target, typedGet);

        // () => _container.Get<T>()
        var lambda = Expression.Lambda(call);

        // _.container.Use<Lazy<T>>(new Lazy<T>(() => _container.Get<T>()));
        typedUse.Invoke(autoMocker, new object[] { Activator.CreateInstance(lazyT, lambda.Compile()) });
    }
}