模拟静态方法Activator.CreateInstance返回另一个类的模拟

时间:2013-06-28 21:49:00

标签: c# mocking moq activator

我有这个工厂类,我想正确测试它。假设我有一个抽象类,它有很多子(继承)。

正如您在我的Factory类中看到的方法BuildChild,我希望能够在运行时创建子类的实例。我必须能够在运行时创建此实例,因为在运行时之前不会知道该类型。并且,我不能将Unity用于此项目(如果是这样,我不会问如何实现这一点。)

这是我要测试的Factory类:

public class Factory
{
    public AnAbstractClass BuildChild(Type childType, object parameter)
    {
        AnAbstractClass child = (AnAbstractClass) Activator.CreateInstance(childType);
        child.Initialize(parameter);
        return child;
    }
}

为了测试这个,我想找到一种方法来Mock Activator.CreateInstance来返回我自己的子类的模拟对象。我怎样才能做到这一点?或者也许如果你有一个更好的方法来不使用Activator.CreateInstance(和Unity),我会打开它,如果它更容易测试和模拟!

我目前正在使用Moq创建我的模拟但是因为Activator.CreateInstance是静态类的静态方法,我无法弄清楚如何做到这一点(我已经知道Moq只能创建对象的模拟实例)。

我看了一下微软的Fakes,但没有成功(我在理解它是如何工作方面遇到了一些困难,并找到了一些解释良好的例子)。

请帮助我!

编辑:

我需要模拟Activator.CreateInstance,因为我想强制此方法返回另一个模拟对象。我想要的正确的事情只是存根这种方法(而不是模仿它)。

所以当我像这样测试BuildChild时:

[TestMethod]
public void TestBuildChild()
{
    var mockChildClass = new Mock(AChildClass);
    // TODO: Stub/Mock Activator.CreateInstance to return mockChildClass when called with "type" and "parameter" as follow.
    var type = typeof(AChildClass);
    var parameter = "A parameter";

    var child = this._factory.BuildChild(type, parameters);
}
使用type和parameter调用的Activator.CreateInstance将返回我的模拟对象,而不是创建真实子类的新实例(尚未实现)。

3 个答案:

答案 0 :(得分:4)

好吧,我很想说这不是你需要模拟的东西,因为它应该被信任进行测试,但我想如果类型来自外部源库,那么你可能会遇到问题......话虽这么说,你可以实现这一点的唯一方法是包装Activator,使其不是一个静态类。

这样的事情:

public ActivatorWrapper
{
    public virtual object CreateInstance(Type type)
    {
        return Activator.CreateInstance(type);
    }
}

答案 1 :(得分:1)

您必须引入一个接口,该接口公开从该类型创建实例的方法。让您的类在其构造函数中实现接口的实现。

然后你可以嘲笑那个。

然后你有一个实现,它只委托你在Production中使用的Activator.CreateInstance。

那就是说,为什么你需要嘲笑这个?为什么你的测试不能检查它是否返回了方法调用中指定的类型?

在编辑之后,为什么不能模拟对BuildChild的工厂调用而不是工厂内的调用。这似乎是你想要模仿的依赖。

似乎你要么测试工厂返回正确的类型,你不需要模拟任何东西,或者你想为你的工厂引入一个接口并模拟它。

我认为想要模拟Activator.CreateInstance是你的代码告诉你,你的设计有些不对劲。

你可以在没有AnAbstractClass实现的情况下测试你的工厂实现,或者我不需要模拟这样的东西:

创建测试实现:

public class TestAnAbstractClass : AnAbstractClass
{
     public object ConstructorParameter;

     public TestAnAbstractClass(object constructorParameter)
     {
          this.constructorParameter = constructorParameter;
     }
}

然后在测试中给你的工厂打电话:

[TestMethod]
public void TestBuildChild()
{
    var type = typeof(TestAnAbstractClass);
    var parameter = "A parameter";

    var child =(TestAnAbstractClass) this._factory.BuildChild(type, parameters);
    Assert.That(child.ConstructorParameter, Is.EqualTo(parameter));
}

然后您正在测试工厂的实际功能,即使实施更改了测试也不需要,并且您测试了所有代码。

答案 2 :(得分:0)

您可以尝试使用环境上下文,如下例所示;

    public static class SystemActivator
    {
        private static Dictionary<Type, object> _mockObjects;

        private static Dictionary<Type, object> MockObjects
        {
            get { return _mockObjects; }
            set
            {
                if (value.Any(keyValuePair => keyValuePair.Value.GetType() != keyValuePair.Key))
                {
                    throw new InvalidCastException("object is not of the correct type");
                }
                _mockObjects = value;
            }
        }

        [Conditional("DEBUG")]
        public static void SetMockObjects(Dictionary<Type, object> mockObjects)
        {
            MockObjects = mockObjects;
        }

        public static void Reset()
        {
            MockObjects = null;
        }

        public static object CreateInstance(Type type)
        {
            if (MockObjects != null)
            {
                return MockObjects.ContainsKey(type) ? MockObjects[type] : Activator.CreateInstance(type);
            }
            return Activator.CreateInstance(type);
        }
    }

这是一个非常基础的课程,可以让您了解可以使用的方法。在测试时,您可以设置MockObjects字典,将类型与要返回的实例(即模拟对象)配对。然后,而不是在您正在构建的类中使用Activator使用SystemActivator。

此类的其他优点是您可以在某种程度上对其进行单元测试,例如测试它在设置时返回你的模拟,在没有时返回一个激活器实例。

Activator永远不会返回与请求的类型不同的实例,SystemActivator应该模仿这种行为,这就是为什么我包含了这个例外。您还可以考虑抛出其他错误(例如,激活器无法使用此示例中显示的CreateInstance重载创建字符串,但如果您将MockObject设置为返回字符串值,则不应允许这样做)

这种方法存在危险,您绝不应该尝试从生产代码中设置MockObjects。为了减少这种危险,我给了方法[Conditional(&#34; DEBUG&#34;)]属性。这不会阻止开发人员尝试在生产代码中设置MockObjects,但这会导致发布版本中断。

此外,在任何使用SystemActivator的测试类中,您需要包含一个重置模拟对象的拆卸方法,否则存在创建测试依赖项的危险。