Spring配置文件和测试

72

我有一个Web应用程序,它需要针对不同环境使用不同的配置文件,其中一些配置被放置在应用服务器上作为JNDI数据源,但是另一些配置则存储在属性文件中。

因此,我想要使用Spring配置文件的特性。

我的问题是我无法让测试用例运行起来。

context.xml:

<context:property-placeholder 
  location="classpath:META-INF/spring/config_${spring.profiles.active}.properties"/>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
    TestPreperationExecutionListener.class
    })
@Transactional
@ActiveProfiles(profiles = "localtest")
@ContextConfiguration(locations = {
    "classpath:context.xml" })
public class TestContext {

  @Test
  public void testContext(){

  }
}

问题似乎是加载配置文件的变量没有被解析:
Caused by: java.io.FileNotFoundException: class path resource [META-INF/spring/config_${spring.profiles.active}.properties] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:157)
at org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties(PropertiesLoaderSupport.java:181)
at org.springframework.core.io.support.PropertiesLoaderSupport.mergeProperties(PropertiesLoaderSupport.java:161)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.postProcessBeanFactory(PropertySourcesPlaceholderConfigurer.java:138)
... 31 more

当前配置文件应使用@ActiveProfile注释进行设置。由于这是一个测试用例,我将无法使用web.xml。如果可能的话,我想避免运行时选项。测试应该按原样运行(如果可能的话)。

我如何正确激活配置文件?可以使用context.xml设置配置文件吗?我能否在调用普通上下文的测试上下文中声明变量?


我对这里所有的答案感到非常困惑。假设我有我的application-test.properties和引用application-test.properties的applicationContext-test.xml。现在我给我的测试类@ActiveProfiles(profiles="localTest"),那么我该如何定义localTest属性呢?我只需创建application-localtest.properties文件,然后在哪里/如何引用该文件?我需要声明名为localTest的配置文件使用application-localtest.properties文件,但我还没有找到一个这样做的例子。 - Skystrider
5个回答

72

我可以建议用这种方式来做,将您的测试定义为:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
    TestPreperationExecutionListener.class
    })
@Transactional
@ActiveProfiles(profiles = "localtest")
@ContextConfiguration
public class TestContext {

  @Test
  public void testContext(){

  }

  @Configuration
  @PropertySource("classpath:/myprops.properties")
  @ImportResource({"classpath:context.xml" })
  public static class MyContextConfiguration{

  }
}

在myprops.properties文件中,具有以下内容:

spring.profiles.active=localtest

通过这样做,您的第二个属性文件应该得到解析:

META-INF/spring/config_${spring.profiles.active}.properties

2
这种方法不够简洁,你需要前往属性文件并修改活动配置文件的值。 - codebusta
是的,我同意... 我正在尝试弄清楚如何使用ActiveProfiles来覆盖特定测试类的默认测试属性,但我不应该更改spring.profiles.active=[value here]以使用我想要使用的测试配置文件... 它需要在测试类中进行注释以确定哪个配置文件处于活动状态。 - Skystrider

10

看了Biju的回答,我找到了一个可行的解决方案。

我创建了一个额外的上下文文件test-context.xml

<context:property-placeholder location="classpath:config/spring-test.properties"/>

包含个人资料:

spring.profiles.active=localtest

并加载测试:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
    TestPreperationExecutionListener.class
    })
@Transactional
@ActiveProfiles(profiles = "localtest")
@ContextConfiguration(locations = {
    "classpath:config/test-context.xml" })
public class TestContext {

  @Test
  public void testContext(){

  }
}

这样做可以在创建多个测试用例时节省一些工作。


1
所以每次你想运行一个测试,你都必须去test-context.xml中更改属性文件路径,如果想要加载另一个属性文件?这与Spring Profiles的理念相矛盾。在这种情况下,请看看我的解决方案。 - codebusta
@CodeBusta 不,这只是一个用于测试的test-context.xml文件,它也会加载常规上下文。 - Udo Held

2

需要使用@EnableConfigurationProperties(您也可以对测试类进行注释),才能加载来自test / resources的application-localtest.yml文件。以下是使用jUnit5的示例:

@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties
@ContextConfiguration(classes = {YourClasses}, initializers = ConfigFileApplicationContextInitializer.class)
@ActiveProfiles(profiles = "localtest")
class TestActiveProfile {

    @Test
    void testActiveProfile(){

    }
}

1
你能否使用“-Dspring.profiles.active=name”命令行参数加载配置文件?似乎上述建议会将你限制在特定的配置中。 - djangofan

1
public class LoginTest extends BaseTest {
    @Test
    public void exampleTest( ){ 
        // Test
    }
}

继承自基础测试类(此示例使用的是testng而不是jUnit,但ActiveProfiles相同):

@ContextConfiguration(locations = { "classpath:spring-test-config.xml" })
@ActiveProfiles(resolver = MyActiveProfileResolver.class)
public class BaseTest extends AbstractTestNGSpringContextTests { }

MyActiveProfileResolver 可以包含任何必要的逻辑来确定使用哪个配置文件:

public class MyActiveProfileResolver implements ActiveProfilesResolver {
    @Override
    public String[] resolve(Class<?> aClass) {
        // This can contain any custom logic to determine which profiles to use
        return new String[] { "exampleProfile" };
    }
}

这将设置配置文件,然后用于解析测试所需的依赖项。

1
最好的方法是删除@ActiveProfiles注释,并执行以下操作:
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
    TestPreperationExecutionListener.class
    })
@Transactional
@ContextConfiguration(locations = {
    "classpath:config/test-context.xml" })
public class TestContext {

  @BeforeClass
  public static void setSystemProperty() {
        Properties properties = System.getProperties();
        properties.setProperty("spring.profiles.active", "localtest");
  }

  @AfterClass
  public static void unsetSystemProperty() {
        System.clearProperty("spring.profiles.active");
  }

  @Test
  public void testContext(){

  }
}

你的test-context.xml文件应该包含以下内容:

<context:property-placeholder 
  location="classpath:META-INF/spring/config_${spring.profiles.active}.properties"/>

2
在 @AfterClass 中使用静态属性而没有进行清理可能会导致严重的、难以分析的麻烦。 - Jan Galinski
2
好的,就像我说的那样:至少在AfterClass方法中通过System.clearProperty()清理属性。或者使用类似https://stefanbirkner.github.io/system-rules/的规则。 - Jan Galinski
我喜欢你的方法,并同意@JanGalinski的观点,即在不确保清理共享状态的情况下使用它存在“尖锐边缘”。 AfterClass建议听起来是整理一切的好方法 - 可能值得修改你的答案。你得到了我的“赞成”,以弥补那个“反对票”,所以不用担心;-) - Eric Kramer
1
我理解,但是在这个特定的例子中为什么需要清理呢?每个测试都使用自己的上下文,因此您不必清理系统属性。如果我错了,请纠正我。我只是添加了所请求的清理部分,但我不明白为什么在这里需要它。 - codebusta
5
不要这样做。我告诉你,这是一种极不可靠的方法。问题是如果没有使用Spring的特殊@ActiveProfiles,则会缓存应用程序上下文。缓存键基于@ContextConfiguration(locations = {})。至于更改系统属性,如果您想并发运行测试,那么您真的在自找麻烦,而且这是完全错误的:“每个测试都使用自己的上下文运行,因此您不必清理系统属性”。只有在使用Surefire/JUnit分叉时才有效。 - Adam Gent
3
请不要使用这种方法。 - Stimpson Cat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接