【问题标题】:How to test Classes with @ConfigurationProperties and @Autowired如何使用 @ConfigurationProperties 和 @Autowired 测试类
【发布时间】:2015-10-23 01:46:05
【问题描述】:

我想测试依赖于加载有@Autowired@ConfigurationProperties 的属性的应用程序的一小部分。我正在寻找一种仅加载所需属性而不总是加载整个ApplicationContext 的解决方案。 这里作为简化示例:

@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestSettings.class, TestConfiguration.class})
public class SettingsTest {
    @Autowired
    TestConfiguration config;

    @Test
    public void testConfig(){
        Assert.assertEquals("TEST_PROPERTY", config.settings().getProperty());
    }
}

配置类:

public class TestConfiguration {
    @Bean
    @ConfigurationProperties(prefix = "test")
    public TestSettings settings (){
        return new TestSettings();
    }
}

设置类:

public class TestSettings {
    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

资源文件夹中的属性文件包含条目:

test.property=TEST_PROPERTY

在我当前的设置中,配置不为空,但没有可用的字段。 字段不是字段的原因应该与我没有使用 Springboot 而是使用 Spring 的事实有关。 那么 Springboot 的运行方式是什么?

编辑: 我想这样做的原因是:我有一个解析文本文件的解析器,使用的正则表达式存储在一个属性文件中。 为了对此进行测试,我只想加载此解析器所需的属性,这些属性位于 TestSettings 上方的示例中。

在阅读 cmets 时,我已经注意到这不再是单元测试了。然而,为这个小测试使用完整的 Spring 引导配置对我来说似乎有点太多了。这就是为什么我问是否有可能只加载一个具有属性的类。

【问题讨论】:

  • 您可以尝试使用 Mockito 的 Whitebox.setInternalState(object, fieldName, value) 方法将值“注入”到对象中。 Example
  • 你可以看看 MockitoAnnotations.initMocks(java.lang.Object) 是如何工作的,并将其扩展到 @ConfigurationProperties@Autowired (可能已经适用,但我不确定)。
  • @Roman Vottner:感谢您的提示。问题是,我想模拟的不仅仅是一个字段,它是 10 个。所以这将是一些写作工作。如果只是那个单元测试,那没什么可抱怨的,但我想要有很多,所以有一个更简单的方法会非常有帮助。
  • 如果您为几个测试“注入”相同类型的对象,只需将指令重构为辅助方法并在这些测试中调用此方法。您还可以参数化此方法以注入某些对象。您还可以考虑一种构建器设置,如果没有调用特定于构建器的方法,则“注入”默认值,如果设置了相应的构建器方法,则设置具体的注入值。
  • 你到底想做什么?从我看到的示例中,您似乎正在测试框架(您不应该)。您可能对此答案感兴趣stackoverflow.com/questions/31692863/…

标签: java properties spring-boot


【解决方案1】:

您需要使用 @EnableConfigurationProperties 注释您的 TestConfiguraion,如下所示:

@EnableConfigurationProperties
public class TestConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "test")
    public TestSettings settings (){
        return new TestSettings();
    }
}

另外你只需要在@ContextConfigurationSettingsTest类中包含TestConfiguration.class

@TestPropertySource(locations = "/SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...

【讨论】:

  • 如果您使用 Spring Boot 和 *.yml 文件,只需删除 @TestPropertySource 并向 @ContextConfiguration 添加一个特殊初始化器,如下所示:@ContextConfiguration(classes = TestConfiguration.class, initializers = ConfigFileApplicationContextInitializer.class)
  • 永远不要忘记为集合添加 setter 和 getter ;)
  • baeldung.com/properties-with-spring 5.3 节也有很好的例子来替代上述解释
【解决方案2】:

几点:

  1. 您的主包中不需要“TestConfiguration”类,因为它所做的只是配置“TestSettings”bean。您可以简单地通过注释 TestSettings 类本身来做到这一点。

  2. 通常您会使用 @SpringApplicationConfiguration 注释加载测试所需的上下文,并传递您的应用程序类的名称。但是,您说您不想加载整个 ApplicationContext(尽管不清楚为什么),因此您需要创建一个特殊的配置类来仅为测试加载。下面我将其称为“TestConfigurationNew”,以避免与您最初拥有的 TestConfiguration 类混淆。

  3. 在Spring Boot的世界里,所有的属性一般都保存在“application.properties”文件中;但可以将它们存储在其他地方。下面,我指定了您建议的“SettingsTest.properties”文件。请注意,您可以拥有此文件的两份副本,一份在 main/resources 文件夹中,一份在 test/resources 文件夹中用于测试。

修改代码如下:

TestSettings.java(在主包中)

@Configuration
@ConfigurationProperties(prefix="test", locations = "classpath:SettingsTest.properties")
public class TestSettings {

    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }
}

SettingsTest.java(在测试包中)

@TestPropertySource(locations="classpath:SettingsTest.properties")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfigurationNew.class)
public class SettingsTest {

    @Autowired
    TestSettings settings;

    @Test
    public void testConfig(){
        Assert.assertEquals("TEST_PROPERTY", settings.getProperty());
    }
}

TestConfigurationNew.java(在测试包中):

@EnableAutoConfiguration
@ComponentScan(basePackages = { "my.package.main" })
@Configuration
public class TestConfigurationNew {
}

现在应该可以按照您想要的方式工作了。

【讨论】:

  • 在单元测试时不应创建 Spring 上下文可能有几个原因。其中之一就是这样一个事实,即在测试时包含 Spring 上下文不是单元测试,而是集成测试。使用 Spring 上下文运行测试(特别是如果在每个方法之后都设置了脏上下文)比正确的单元测试需要更多的时间来执行。此外,使用启用的 Spring 上下文来测试单个测试方法的失败注入等边缘情况更难设置。
  • @RomanVottner - 我同意,但是一旦 OP 表明他正在检查设置是否从属性文件正确加载,我认为他已经进入集成测试领域,现在需要更完整的上下文。
  • 我收到 java.lang.IllegalStateException: 无法在 org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext 加载 ApplicationContext(DefaultCacheAwareContextLoaderDelegate.java:124
  • 是否可以加载 YML 而不是属性?
  • 这并不能解决OP提出的问题,只是提供了一个替代方案
【解决方案3】:

您实际上可以直接将@EnableConfigurationProperties 添加到您的@SpringBootTest。
例如:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestConfiguration.class)
@EnableConfigurationProperties
...

【讨论】:

  • '@EnableConfigurationProperties 对我有帮助。我只是使用'@RunWith(SpringJUnit4ClassRunner.class) - 测试运行器'@TestPropertySource() 来重新定义测试'@EnableConfigurationProperties(UnitProperties.class) 的属性 - 允许初始化标有@ConfigurationProperties 的配置bean。
【解决方案4】:

如果你使用 Spring Boot,现在你只需要:

@RunWith(SpringRunner.class)
@SpringBootTest

没有额外的@ContextConfiguration,没有额外的课程只用于EnableAutoConfigurationEnableConfigurationProperties的测试。您不必指定要加载的配置类,它们都会被加载。

但是,请确保您要在main/resources/application.yml 中读取的属性条目也存在于test/resources/application.yml 中。重复是不可避免的。


另一种方式是:

  1. 在同一级别定义一个仅用于测试的配置类以及MyApplicationTest.java。此类可以为空。

喜欢:

@EnableAutoConfiguration
@EnableConfigurationProperties(value = {
        ConnectionPoolConfig.class
})
public class MyApplicationTestConfiguration {
}
  1. 并且,在类中加载自动装配的配置。

喜欢:

@RunWith(SpringRunner.class)
//@SpringBootTest // the first, easy way
@ContextConfiguration(classes = MyApplicationTestConfiguration.class,
        initializers = ConfigFileApplicationContextInitializer.class)
public class ConnectionPoolConfigTest {

    @Autowired
    private ConnectionPoolConfig config;

基本上,你:

  • @EnableConfigurationProperties@EnableAutoConfiguration使用特定配置,列出你要加载的所有@ConfigurationProperties文件
  • 在测试类中,加载这个测试配置文件,使用Spring定义的初始化类加载application.yml文件。

然后,将要加载的值放入test/resources/application.yml。重复是不可避免的。如果您需要加载另一个文件,请使用带有位置的@TestProperties()注意@TestProperties 仅支持.properties 文件。


两种方式都适用于配置类加载值

  • 来自application.yml/application.properties
  • 或来自另一个属性文件,由PropertySource 指定,如@PropertySource(value = "classpath:threadpool.properties")

重要

Spring 文档的最后注释,根据 here

有些人使用 Project Lombok 来自动添加 getter 和 setter。确保 Lombok 不会为此类类型生成任何特定的构造函数,因为容器会自动使用它来实例化对象。

最后,只考虑标准 Java Bean 属性,不支持绑定静态属性。

这意味着,如果你有lombok.@Builder 没有@NoArgsConstructor@AllArgsConstructor,属性注入将不会发生,因为它只看到@Builder 创建的不可见构造函数。因此,请务必不使用或全部使用这些注释!

【讨论】:

  • 如果您不需要 @SpringBootTest 提供的所有内容,则不应使用它。而且我认为测试属性是这种情况,最好不要使用它。您可以使用@SpringJUnitConfig 代替它。
  • 第二种方式不使用SpringBootTest
  • 这么少的帖子里有大量的好信息!我的关键点是“并且,将要加载的值放入 test/resources/application.yml。重复是不可避免的”
【解决方案5】:

单元测试

为了避免加载 Spring 上下文,我们可以使用 Binder 类,它也是 Spring 的 used internally

// A map of my properties.
Map<String, String> properties = new HashMap<>();
properties.put("my-prefix.first-property", "foo");
properties.put("my-prefix.second-property", "bar");

// Creates a source backed by my map, you can chose another type of source as needed.
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties)

// Binds my properties to a class that maps them.
Binder binder = new Binder(source);
BindResult<MyConfiguration> result = binder.bind("my-prefix", MyConfiguration.class);

// Should return true if bound successfully.
Assertions.assertTrue(result.isBound);

// Asserts configuration values.
MyConfiguration config = result.get();
Assertions.assertEquals("foo", config.getFirstProperty());
Assertions.assertEquals("bar", config.getSecondProperty());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-30
    • 1970-01-01
    相关资源
    最近更新 更多