我正在尝试增加应用程序的单元测试覆盖率,但在模拟注入到我要测试的类中的基类的子类时遇到了问题。我正在尝试测试
ActionController
类,并特别尝试模拟 _childClassHandler.DoSomething(myString, payload)
调用。
public class ActionHandler : IActionHandler
{
private readonly IAction _action;
public ActionHandler(IAction action)
{
_action = action;
}
public void DoSomething(string myString, string payload)
{
//do stuff//
}
}
public class ChildClassHandler : ActionHandler
{
public ChildClassHandler(IAction childClass) : base(childClass) { }
}
public class ActionController
{
private readonly IActionHandler _actionHandler;
private readonly IChildClassHandler _childClassHandler;
public ActionController(IActionHandler actionHandler)
{
_actionHandler = actionHandler;
_childClassHandler = UnityHelper.GetContainer().Resolve<ChildClassHandler>();
}
public HttpResponseMessage PostMessage(string myString, string payload)
{
//do validations im trying to unit test//
_childClassHandler.DoSomething(myString, payload);
//return stuff//
}
}
我遇到了这个 s/o question ,它看起来很相似,但所有建议的解决方案都是重构,而且问题并不完全相同,所以我想确保在重构之前耗尽所有资源。我还尝试了这个 s/o question 的解决方案,因为这些类没有接口,但不起作用。所以我的问题是,我是否能够模拟注入到类控制器中的接口的子类?或者该方法始终是静态的,并且要对其进行单元测试,我需要重构代码吗?
如果需要更多信息,请告诉我。
编辑:
添加UnityHelper.GetContainer()方法:
private static readonly Lazy<IUnityContainer> _container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
RegisterAppSettings(container);
container.LoadConfiguration();
return container;
});
/// <summary>
/// Entry point for instantiating the helper and capturing the IUnityContainer.
/// </summary>
/// <returns></returns>
public static IUnityContainer GetContainer()
{
return _container.Value;
}
至少正如 OP 中的一个链接所暗示的那样,这不是一个好的设计,因此最好的长期解决方案是更改代码,以便更容易测试。如果代码变得更容易测试,它也会变得更容易使用。
如果您无法更改代码,那么您将不得不解决尴尬的问题。不过,这应该是可能的,尽管我已经十多年没有专门使用 Unity 了。
我假设
UnityHelper.GetContainer
使用生产服务配置容器。因此,当 ActionController
构造函数调用 UnityHelper.GetContainer().Resolve<ChildClassHandler>()
时,它会接收配置用于生产用途的 ChildClassHandler
,而不是可以用 Test Double 替换的对象。
据我记得,Unity 容器始终是可变的,因此您可以尝试从测试中覆盖容器。 C#-near 伪代码如下。这里我假设最小起订量,但如果愿意的话,您应该能够修改示例以使用另一个动态模拟库。
var container = UnityHelper.GetContainer();
var origCch = container.Resolve<ChildClassHandler>();
try
{
var cchTD = new Mock<ChildClassHandler>();
// Overwrite the ChildClassHandler registration
container.RegisterInstance(cchTD.Object);
var ahTD = new Mock<IActionHandler>();
var sut = new ActionController(ahTD.Object);
// Put the rest of the test code here...
}
finally
{
// Restore the original service
container.RegisterInstance(origCch);
}
您需要在
try/finally
块中执行此操作,以确保每次测试后全局状态都能正确重置,即使测试失败也是如此。否则,您的一个测试可能会失败,从而导致其他测试也失败的连锁反应。
除非现代版本的 Unity 是线程安全的,否则您还需要禁用测试框架的(默认?)并行化,因为您现在正在更改全局可变状态。
正如一开始提到的,这种设计会带来一系列的问题。测试很难编写只是被测系统僵化的一个症状。
这样的事情是否有效取决于 Unity 是否使用 last-registration-wins 策略。根据我自己的书,我写的时候确实如此,但那是十多年前的事了。