我有这个工厂类,我想正确测试它。假设我有一个抽象类,它有很多子(继承)。
正如您在我的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将返回我的模拟对象,而不是创建真实子类的新实例(尚未实现)。
答案 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的测试类中,您需要包含一个重置模拟对象的拆卸方法,否则存在创建测试依赖项的危险。