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

时间:2015-01-05 18:33:41

标签: c# unit-testing moq

使用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

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

2 个答案:

答案 0 :(得分:1)

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

    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的通用方法。

答案 1 :(得分:0)

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

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