如何使用依赖注入模拟泛型工厂



使用Ninject,我有一个像这样的工厂注册:

kernel.Bind<IMyFactory>().ToFactory();

IMyFactory看起来像这样的地方:

public interface IMyFactory
{
    T CreateInstance<T> where T : MyBaseClass;
}

这样做的原因是从MyBaseClass派生的类有自己的依赖项,我需要能够从它们的类型创建这些类的实例,我这样做是这样的:

MethodInfo mi = factory.GetType().GetMethod("CreateFoo");
MethodInfo generic = mi.MakeGenericMethod(type);
var param = (MyBaseClass)generic.Invoke(factory, null);

其中factory是 Ninject 创建的IMyFactory实例,type是我要创建的派生类的类型MyBaseClass。这一切都非常有效。

问题是我希望能够使用 Visual Studio 和 Moq 中的测试框架对此进行单元测试,但我找不到模拟IMyFactory的好方法。

目前我有这个:

myFactory = new Mock<IMyFactory>();
myFactory.Setup(d => d.CreateFoo<MyFirstClass>()).Returns(new MyFirstClass(userService.Object));
myFactory.Setup(d => d.CreateFoo<MySecondClass>()).Returns(new MySecondClass(userService.Object, someOtherServiceMock.Object));
// repeat for as many classes as would usually be handled by IMyFactory

显然,这真的很乏味(我有几十节课要做)。有没有更好的方法?

这有点繁琐,我不确定这是否是正确的测试策略,但这里有一种方法似乎有效。我在测试类中创建了一个函数,如下所示:

    private Dictionary<Type, Mock> mocks;
    private void SetupCreateFoo<T>(Mock<IMyFactory> myFactory) where T: MyBaseClass 
    {
        var t = typeof(T);
        var constructors = from constructor in t.GetConstructors()
                           let parameters = constructor.GetParameters()
                           where parameters.Length > 0
                           select new { constructor, parameters };
        // Note: there should only be one constructor anyway           
        foreach (var c in constructors)
        {
            List<object> parameters = new List<object>();
            foreach (var p in c.parameters)
            {
                parameters.Add(mocks[p.ParameterType].Object);
            }
            myFactory.Setup(d => d.CreateAnalytic<T>()).Returns((T)c.constructor.Invoke(parameters.ToArray()));
        }
    }

其中字典mocks是从 MyBaseClass 派生的类可能需要的模拟服务的集合。

现在,在设置测试类的位置,我可以执行以下操作:

    [TestInitialize]
    public void Setup()
    {
        userService = new Mock<IUserService>();
        //... setup userService
        someOtherServiceMock = new Mock<ISomeOtherService>();
        //... setup someOtherService
        mocks = new Dictionary<Type, Mock>()
        {
            { typeof(IUserService), userService },
            { typeof(ISomeOtherService), someOtherServiceMock}
        };
        myFactory= new Mock<IMyFactory>();
        SetupCreateFoo<MyFirstClass>(myFactory);
        SetupCreateFoo<MySecondClass>(myFactory);
        // ... etc
        finderService = new FinderService(userService.Object, myFactory.Object);
    }

而且我认为我可能会设置一个List<Type>并在循环中调用SetupCreateFoo<T>以进一步减少重复,但是使用 Type 调用通用方法需要再次进行一些反思。

我仍然不清楚为什么你需要嘲笑IMyFactory,但要回答你的核心问题:不,Moq 现在提供了为任何泛型类型参数设置泛型方法的方法。我为一个与Moq集成的开源项目做出了贡献,我以前也遇到过这个限制。

但是,您应该只为与测试相关的泛型类型参数设置方法。如果你的测试需要在工厂设置满是类型参数的手,那么这听起来要么像是代码异味,要么是异国情调的边缘情况。

最新更新