【问题标题】:How to mock a decorated class in an integration test如何在集成测试中模拟装饰类
【发布时间】:2018-01-25 15:28:22
【问题描述】:

我们在项目中使用 Castle Windsor。我们有 Installer : IWindsorInstaller 类,其中包含 Install 方法以及特定项目的所有注册。

现在我想编写一个集成测试,它将在所有类上测试一些“快乐场景”,只使用模拟数据源。这意味着我想使用模拟数据源而不是生产代码中使用的普通 SQL 数据源。

问题是由使用 Castle Windsor 的装饰器支持来装饰数据源的事实引起的。这意味着我首先注册装饰器链(以某种想要的顺序),然​​后注册真正的数据源。集成测试想要调用生产代码的Install 方法,但需要放一个模拟数据源而不是真实数据源。如果没有装饰器,我们可以简单地使用IsDefault 来强制容器使用模拟而不是最初注册的生产类。但这对于装饰器是不可能的,因为它会覆盖整个装饰器链。

在生产代码中,链就像:客户端 -> 缓存 -> 计算 -> 数据源

在集成测试中,我想要这样的链:客户端 -> 缓存 -> 计算 -> MockedData

Install 中的代码如下:

container.Register(Component.For<IRepository, ICacheService<int, List<MyData>>>().ImplementedBy<MyDataCache>());
container.Register(Component.For<IRepository>().ImplementedBy<ComputedColumns>());
container.Register(Component.For<IRepository>().ImplementedBy<DataRepository>());

我现在需要的是跳过最后一个注册。但是如果我在集成测试中单独编写一个注册码,它不会测试Install方法是否正确编写。我正在尝试找到一个可以与生产代码中的安装方法一起使用的解决方案。

到目前为止我尝试了什么:由于我没有找到任何真正的温莎城堡解决方案,我将Install 方法拆分为两个方法,生产代码调用它们来注册所有内容,但集成测试没有调用包含最后一次注册的那个。不幸的是,此解决方案要求生产代码以特定顺序调用这两个 Install 方法,否则它不起作用。因此,我再次创建了一些危险的代码,这些代码无法通过正常的 NUnit 测试来测试。 (这是阅读,我错误地颠倒了这些注册的顺序,直到有人在生产代码中使用它才可见......)

【问题讨论】:

    标签: .net integration-testing inversion-of-control decorator castle-windsor


    【解决方案1】:

    在生产代码中,链就像:客户端 -> 缓存 -> 计算 -> 数据源

    在集成测试中,我想要这样的链:客户端 -> 缓存 -> 计算 -> MockedData

    从这样的任务表述中,我很清楚Installer 应该使用数据源的类型(DataSource 或MockedData)进行参数化。

    由于Installer 实现了Castle IWindsorInstaller 接口并且您无法更改方法Install,您可以将Installer 类设为具有数据源类型的泛型:

    public class Installer<TDataRepository> : IWindsorInstaller where TDataRepository : IRepository
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<IRepository, ICacheService<int, List<MyData>>>().ImplementedBy<MyDataCache>());
            container.Register(Component.For<IRepository>().ImplementedBy<ComputedColumns>());
            container.Register(Component.For<IRepository>().ImplementedBy<TDataRepository>());
        }
    }
    

    现在您可以设置所需的实现,不同的生产代码和集成测试:

    //  In production code
    container.Install(new Installer<DataRepository>());
    
    //  In integration test
    container.Install(new Installer<MockedRepository>());
    

    这种解决方案没有您描述的“两种方法”方法的任何缺点。

    【讨论】:

    • 我们不能完全做到这一点,因为我们的安装程序是按照约定自动调用的。我的意思是我的代码在插件中,我无权访问容器。系统只是扫描所有 IWindsorInstaller 实现的程序集并调用它们。
    • 我认为这个答案更像是一种解决方法,而不是我的问题的真正解决方案。当然,我可以自己进行注册。我想要实现的是能够在集成测试中使用真正的生产代码安装程序,并通过在我调用真正的生产代码安装程序之前或之后做一些事情来调整一些特定的注册。
    • 关于按惯例注册 - 您可以使通用安装程序成为一个抽象类(例如InstallerT&lt;TDataRepository&gt;),然后具有仅继承自InstallerT&lt;DataRepository&gt; 的具体实现。 Windsor 会正确调用它。
    • 关于您的第二条评论 - 我看不到修改在Install() 方法中设置的装饰器链的其他可能性。您可以在之前或之后添加更多装饰器,但这不会帮助您避免调用 DataSource 实现。
    猜你喜欢
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 2012-11-01
    • 2019-12-07
    • 2016-08-20
    • 2021-04-06
    • 2011-09-30
    • 1970-01-01
    相关资源
    最近更新 更多