模拟在构造函数中初始化的子类

问题描述 投票:0回答:1

我正在尝试增加应用程序的单元测试覆盖率,但在模拟注入到我要测试的类中的基类的子类时遇到了问题。我正在尝试测试

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;
}
c# .net unit-testing mocking unity-container
1个回答
0
投票

至少正如 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 策略。根据我自己的书,我写的时候确实如此,但那是十多年前的事了。

© www.soinside.com 2019 - 2024. All rights reserved.