72

I've got a web application where I have the typical problem that it requires different configuration files for different environments. Some configuration is placed in the application server as JNDI datasources, however some configuration stays in property files.

Therefore I want to use the Spring profiles feature.

My problem is that I don't get the test case running.

context.xml:

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

Test:

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

  @Test
  public void testContext(){

  }
}

The problem seems to be that the variable for loading the profile isn't resolved:

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

The current profile should be set with the @ActiveProfile annotation. As it's a test case I won't be able to use the web.xml. If possible I'd like to avoid runtime options as well. The test should run as is (if possible).

How can I properly activate the profile? Is it possible to set the profile with a context.xml? Could I declare the variable in a test-context.xml that is actually calling the normal context?

kryger
  • 12,906
  • 8
  • 44
  • 65
Udo Held
  • 12,314
  • 11
  • 67
  • 93
  • I'm so confused by all the answers here. Lets say I have my application-test.properties and applicationContext-test.xml which references application-test.properties. Now I give my test class @ActiveProfiles(profiles="localTest") how do I *define* the localTest properties? Do I just make application-localtest.properties and then where/how do I reference that file? I need to declare that profile named *localTest* uses the application-localtest.properties file but I haven't found an example of doing that. – Skystrider Jan 31 '22 at 22:26

5 Answers5

71

Can I recommend doing it this way, define your test like this:

@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{

  }
}

with the following content in myprops.properties file:

spring.profiles.active=localtest

With this your second properties file should get resolved:

META-INF/spring/config_${spring.profiles.active}.properties
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • 2
    This approach is not clean, you will have to go to the properties file where the active profile value should be changed. – codebusta Jun 02 '15 at 07:30
  • Yeah I agree... I'm trying to figure out how to use ActiveProfiles to override my default test properties for specific test class but I shouldn't be changing spring.profiles.active=[value here] to the test profile I want to use... it needs to be annotation in the test class that determines which profile is active. – Skystrider Jan 31 '22 at 22:21
10

Looking at Biju's answer I found a working solution.

I created an extra context-file test-context.xml:

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

Containing the profile:

spring.profiles.active=localtest

And loading the test with:

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

  @Test
  public void testContext(){

  }
}

This saves some work when creating multiple test-cases.

Udo Held
  • 12,314
  • 11
  • 67
  • 93
  • 1
    So every time you want to run a test you will have to go to test-context.xml and change the properties file path, if want to load another property? It is contradicting the idea of Spring Profiles. Have a look at my solution in this case. – codebusta Jun 02 '15 at 07:33
  • @CodeBusta No, it's a test-context.xml just for the test which loads the regular context as well. – Udo Held Jan 18 '18 at 05:06
2

@EnableConfigurationProperties needs to be there (you also can annotate your test class), the application-localtest.yml from test/resources will be loaded. A sample with jUnit5

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

    @Test
    void testActiveProfile(){

    }
}
unk
  • 129
  • 1
  • 3
  • 1
    Can you get the config to load from command line using `-Dspring.profiles.active=name` ?? Seems the above suggestion fixes you into a specific config. – djangofan Oct 18 '21 at 19:51
1

The best approach here is to remove @ActiveProfiles annotation and do the following:

@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(){

  }
}

And your test-context.xml should have the following:

<context:property-placeholder 
  location="classpath:META-INF/spring/config_${spring.profiles.active}.properties"/>
F.O.O
  • 4,730
  • 4
  • 24
  • 34
codebusta
  • 1,922
  • 1
  • 14
  • 15
  • 2
    Using static properties without them cleaning up in @AfterClass can lead to serious, hard to analys trouble. – Jan Galinski Apr 29 '16 at 13:26
  • 2
    Well, as I said: at least clean up the properties via System.clearProperty() in an AfterClass-method. Or use some rule like https://stefanbirkner.github.io/system-rules/ – Jan Galinski May 02 '16 at 08:50
  • I like your approach and agree with @JanGalinski that there are "sharp edges" to using shared state without ensuring it's cleaned up. The AfterClass suggestion sounds like a good way to tidy everything up--might be worth revising your answer. You got my "upvote" to make up for that downvote so no worries ;-) – Eric Kramer Nov 21 '16 at 16:12
  • 1
    I understand, but what's the reason for cleaning up in this particular example? Each test is run with it's own context, therefore you don't have to clean up System properties. Correct me if I'm wrong. I just added the requested cleaning up part, but I don't get why it is needed in here. – codebusta Nov 28 '16 at 11:39
  • 5
    DO NOT DO THIS. I will tell you this is an extremely unreliable approach. The issue is without using Spring's special @ActiveProfiles **it will cache the application context**. The cache key is based on `@ContextConfiguration(locations = {})`. As for changing system properties you are really **#!$#@* yourself if you want to run tests concurrently and this is completely wrong: "*Each test is run with it's own context, therefore you don't have to clean up System properties*" . That only works if you have Surefire/JUnit forking. – Adam Gent Apr 19 '17 at 15:15
  • 3
    Just do not use this approach. – Stimpson Cat Aug 03 '17 at 15:30
1
public class LoginTest extends BaseTest {
    @Test
    public void exampleTest( ){ 
        // Test
    }
}

Inherits from a base test class (this example is testng rather than jUnit, but the ActiveProfiles is the same):

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

MyActiveProfileResolver can contain any logic required to determine which profile to use:

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" };
    }
}

This sets the profile which is then used to resolve dependencies required by the test.

infojolt
  • 5,244
  • 3
  • 40
  • 82