当我有很多 SpringBootTest 类 时,如何有效地使用嵌套配置 类 来注入依赖项

How can I efficiently use nested configuration classes to inject dependencies when I have many SpringBootTest classes

这个问题是sequel到

接受的答案是在每个 @SpringBootTest 测试夹具 class 中定义一个嵌套的 class,用 @TestConfiguration 注释它并在其中定义一个工厂方法对于需要解析的每个 bean。嵌套 classes 的影响仅限于测试夹具,影响夹具中的所有测试,但不影响其他夹具中定义的测试。

当 运行 每个测试夹具中的测试时,这提供了对注入组件的依赖项的细粒度控制。

这种方法的问题是它需要在每个测试装置 class 中添加一个嵌套解析器 class。 这是不可扩展的。 考虑一个有 10 个测试夹具的项目。其中 9 个使用相同的注入依赖项,只有第 10 个需要对一个特定接口使用不同的实现。

在这种情况下,我需要将测试配置 class 复制到 9 个测试夹具 classes 中,并使用第二个配置 class 仅用于第 10 个测试。

我需要一种更具可扩展性的方法来执行此操作。例如,在上面的例子中,我希望能够定义两个配置 classes,一个用于测试夹具使用的两个配置中的每一个。然后我希望能够为每个测试夹具指定应该使用两个配置 classes 中的哪一个。 我试过:

  1. 我尝试导入一个文本的嵌套配置class 使用 @Import 注释将 fixture 放入另一个测试 fixture 后者,但这样做时,配置 class 被忽略 后者。
  2. 我还尝试将嵌套配置 class 移动到 上层,以便它可以用于所有测试装置 没有明确定义一个不同的嵌套class,但是在这个 如果配置 class 被所有测试装置忽略。

所以总而言之,我正在寻找一种有效的方法,允许我只编写每个配置 class 一次,然后有选择地将一个应用到每个 SpringBootTest class 而无需复制它。

经过一些实验,我得出了以下解决方案。 我也会添加所有细节,总结我在 中学到的知识。

背景

  1. 我们有两个接口:IClient 和 IServer
  2. IClient 有两种实现方式:RealClient 和 MockClient。
  3. IServer有两种实现:RealServer和MockServer。

要求

  1. 生产代码(在 main/java 中)应该使用两者的真实实现。
  2. 测试夹具(在test/java中用@SpringBootTest注释)

    • InterfaceTests 定义了应该使用 MockServer 和 MockClient 的测试
    • ClientTests 定义了应该使用 MockServer 和 RealClient 来测试 RealClient 的测试。
    • ServerTests 定义了应该使用 MockClient 和 RealServer 来测试 RealServer 的测试。
    • IntegrationTests 定义了应该使用 RealServer 和 RealClient 的测试

从上面可以看出,mock/real client/server 有四种组合,并且代码的某些区域需要每种组合。

解决方案

此解决方案使用@Configuration 和@TestConfiguration 注释来实现这些要求,而无需重复代码。

  1. 不要使用 @Component
  2. 注释接口及其实现
  3. 在main/java下实现一个配置class如下:

@Configuration
public class RealInjector {
    @Bean
    public IServer createServer(){
        return new RealServer();
    }

    @Bean
    public IClient createClient(){
        return new RealClient();
    }
}

  1. 在test/java下实现这三个测试配置classes
@TestConfiguration
public class AllMockInjector {
    @Bean
    public IServer createServer(){
        return new MockServer();
    }

    @Bean
    public IClient createClient(){
        return new MockClient();
    }
}

@TestConfiguration
public class MockServerInjector{
    @Bean
    public IServer createServer(){
        return new MockServer();
    }

    @Bean
    public IClient createClient(){
        return new RealClient();
    }
}

@TestConfiguration
public class MockClientInjector{
    @Bean
    public IServer createServer(){
        return new RealServer();
    }

    @Bean
    public IClient createClient(){
        return new MockClient();
    }
}

  1. 如下注释InterfaceTests测试夹具:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {AllMockInjector.class})
public class InterfaceTests { ... }
  1. 如下注释 ClientTests 测试夹具:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MockServerInjector.class})
public class ClientTests { ... }
  1. 如下注释 ServerTests 测试夹具:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MockClientInjector.class})
public class ServerTests { ... }
  1. 如下注释 IntegrationTests 测试夹具:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {RealInjector.class})
public class IntegrationTests { ... }

终于

为了测试配置 classes 从 main/java 覆盖 RealInjector 配置 class 我们需要设置 属性:

spring.main.allow-bean-definition-overriding=true 

一种方法是按如下方式注释上述每个测试装置:

@SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
class TestFixture { ... }

但这非常冗长,尤其是当您有很多测试装置时。 相反,您可以在 test/resources 下的 application.properties 文件中添加以下内容:

spring.main.allow-bean-definition-overriding=true

您可能还需要将其添加到 main/resources 下的 application.properties 中。

总结

此解决方案使您可以对注入到生产和测试代码中的实现进行细粒度控制。该解决方案不需要代码重复或外部配置文件(test/resources/application.properties 中的 属性 除外)。