【问题标题】:Mock static method Activator.CreateInstance to return a mock of another class模拟静态方法 Activator.CreateInstance 返回另一个类的模拟
【发布时间】:2013-06-28 21:49:00
【问题描述】:

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

正如您在我的 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;
    }
}

为了测试这一点,我想找到一种方法来模拟 Activator.CreateInstance 以返回我自己的子类的模拟对象。我怎样才能做到这一点?或者,如果您有更好的方法来做到这一点而不使用 Activator.CreateInstance(和 Unity),如果它更容易测试和模拟,我愿意接受!

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

我查看了来自 Microsoft 的 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);
}

使用类型和参数调用的 Activator.CreateInstance 将返回我的模拟对象,而不是创建真实子类的新实例(尚未实现)。

【问题讨论】:

    标签: c# mocking moq activator


    【解决方案1】:

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

    类似这样的:

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

    【讨论】:

    • 当你说这应该被信任时你是完全正确的,所以我不需要嘲笑它。我只是编辑我的帖子来解释为什么我需要模拟这个类。
    • @Jeep87c 这就是为什么我也为此提供了解决方法。除非你想进入更强大的框架......但那些会促进不良的 OO 行为
    • 哦,抱歉,您可能在我编辑我的帖子时编辑了您的帖子。没关系,感谢您提供此解决方法。因为你说的原因,我不想使用更强大的框架。
    【解决方案2】:

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

    然后你可以模拟它。

    然后你有一个实现,它只委托给你在生产中使用的 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));
    }
    

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

    【讨论】:

    • 因为测试本身的目的是测试Factory类及其方法。嘲笑工厂电话只会以无用的测试结束。当我使用它测试其他类时,我已经模拟了这个工厂。
    • 另外,对于您的问题,“为什么您的测试不能检查它是否返回方法调用中指定的类型?”因为子类还没有实现,根据定义,一个类的单元测试不应该依赖于其他类。
    • 但一路下来都是乌龟。如果您引入一个包装 Activator.CreateInstance 的类,您将如何测试该实现?我想您将能够测试返回的对象是否调用了它的初始化方法。但是初始化方法是一种气味,应该避免。参数可能应该通过在您的抽象类上声明的构造函数传递(因为它似乎参数不是可选的),而不是这会使该方法“空”,因为它只会包装对 Activator.CreateInstance (参数)的调用
    • @jeep87c 我认为您试图解决代码中的另一个问题。但是包装函数会起作用。
    • 您的评论非常正确!就像你说的那样,它一直是乌龟,但是,我可以像@JustinPihony 建议的那样生活一个类,因为它只是一个静态方法的包装器。但我不能在我的工厂没有测试。您对“参数”的看法也更正确。它应该直接传递给构造函数。我会多看一点 CreateInstance 方法,看看如何将我的参数传递给构造函数。
    【解决方案3】:

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

        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("DEBUG")] 属性。这不会阻止开发人员尝试在生产代码中设置 MockObjects,但会导致发布版本中断。

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

    【讨论】:

      猜你喜欢
      • 2019-08-18
      • 1970-01-01
      • 2017-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-20
      • 1970-01-01
      相关资源
      最近更新 更多