Alex opened SPR-8982 and commented

Status Quo

Support for setting the active bean definition profiles in an integration test within the Spring TestContext Framework (TCF) is currently limited to one of the following techniques:

  1. Providing a hard-coded list of profiles declared via the value or profiles attribute of @ActiveProfiles - since Spring Framework 3.1
  2. Specifying a custom ActiveProfilesResolver (which programmatically determines the active profiles) declared via the resolver attribute of @ActiveProfiles - since Spring Framework 4.0

In stand-alone or production code, Spring honors the spring.profiles.active system property for determining the set of active profiles to use when loading an ApplicationContext; however, the TCF ignores this system property, relying solely on configuration via @ActiveProfiles.


Proposals

  1. Provide the ability to override @ActiveProfiles specified in an integration test via the spring.profiles.active system property or some other system property specific to the TCF.
  2. Set the spring.profiles.active system property to the value specified via @ActiveProfiles
  3. See comments in this issue for background.
  4. This point likely warrants its own JIRA issue for proper consideration.

Implementation Notes

  • Consider introducing a new boolean flag in @ActiveProfiles that allows profiles declared via the annotation to be overridden by those defined via the spring.profiles.active system property (if it is present).

Further Resources

Related discussions at Stack Overflow:


Affects: 3.1 GA

Issue Links: - #12410 Decide what to do with @IfProfileValue - #9538 Introduce strategy for determining if a profile value is enabled for a particular test environment - #13625 SystemProfileValueSource is not very compatible with the new 3.1 default system property profiles - #16300 Introduce annotation to skip test based on active Spring profile - #15761 spring.profiles.active is not honored when building the context cache key in the TestContext framework

19 votes, 24 watchers

Comment From: spring-projects-issues

Peter De Winter commented

That or it should set the property spring.profiles.active.

Comment From: spring-projects-issues

Sam Brannen commented

Peter De Winter, what do you mean by it?

Comment From: spring-projects-issues

Peter De Winter commented

When running tests with a certain active profile the environment property spring.profiles.active is not being set.

Using ${spring.profiles.active} placeholders inside your XML namespace application context this placeholder is not resolved when launching tests with @ActiveProfiles.

Example:

<context:property-placeholder
            ignore-resource-not-found="false" ignore-unresolvable="false"
            location="classpath:/properties/${spring.profiles.active:test}/external.properties"/>

Now that I read the issue description again it might be this is not completely same request, but the issue reason might be the same...

Comment From: spring-projects-issues

Martin Flower commented

I would also like the @ActiveProfiles annotation to set the value of spring.profiles.active. This will enable me to write

@Configuration
@PropertySource({"classpath:props/configuration.properties","classpath:props/${spring.profiles.active}/configuration.properties"})
public class AppConfigProperties {
...

And my integration tests will inherit from a parent class annotated with @ActiveProfiles("integrationtest") and will pick up an AppConfigProperties which is configured using props/integrationtest/configuration.properties.

At the moment I cannot see how to set spring.profiles.active from the integration test base class (extends AbstractTransactionalTestNGSpringContextTests) as the ApplicationContext seems to be loaded before I can call any processing.

Comment From: spring-projects-issues

Sam Brannen commented

Martin Flower,

At the moment I cannot see how to set spring.profiles.active from the integration test base class (extends AbstractTransactionalTestNGSpringContextTests) as the ApplicationContext seems to be loaded before I can call any processing.

If you need to set the spring.profiles.active system property before the TestContext framework loads your ApplicationContext, you could always do so in a before class method. With TestNG, this would be in an @BeforeClass method, and you could likely achieve the same with an @BeforeSuite or @BeforeTest method.

Regards,

Sam

Comment From: spring-projects-issues

Martin Flower commented

Thanks Sam. At the moment I have the following abstract base classes

@ContextConfiguration(locations = {"/test-context.xml",
                                   "/test-db-context.xml",
                                   "/test-security-context.xml"})
public abstract class AbstractUnitTestWithDB extends AbstractTransactionalTestNGSpringContextTests {
    // Unfortunately @ActiveProfiles annotation does not set the environment variable,
    // which is needed by AppConfigProperties
    static {
        // Needs to be called before the Application Context is loaded
        System.setProperty("spring.profiles.active", "unittest");
    }
}
@ContextConfiguration(locations = {"/integration-context.xml",
                                   "/integration-security-context.xml"})
@TransactionConfiguration(defaultRollback = false)
public abstract class AbstractIntegrationTestWithDB extends AbstractTransactionalTestNGSpringContextTests {
    static {
        // Needs to be called before the Application Context is loaded
        System.setProperty("spring.profiles.active", "integrationtest");
    }
}

So, I'm using a static {} block. My original intention was to use @ActiveProfiles annotation instead, which I couldn't get to work.

Would be putting this code in a @BeforeClass method be an improvement? Maybe ? : )

/Martin

Comment From: spring-projects-issues

Peter De Winter commented

Thanks Sam Brannen. This was indeed a solution to our specific problem.

Comment From: spring-projects-issues

Sam Brannen commented

Martin Flower, if you've already got that code in static blocks, then there isn't really any functional difference between that and using an @BeforeClass method. The only difference is that using an @BeforeClass method might make the intent more obvious. ;)

Comment From: spring-projects-issues

Sam Brannen commented

Peter De Winter, I'm glad that little trick solves your problem!

In fact, it might remain the only way to have the system property set. Even if we implement the original intent of this issue (i.e., allowing the presence of the system property to override @ActiveProfiles), it might be the case that Spring does not set the system property for you. Of course, the details have not yet been decided, but I am leaning toward not setting the system property since that could have adverse side effects on other tests running within the same JVM.

Regards,

Sam

Comment From: spring-projects-issues

Sam Brannen commented

For further ideas on how to set active profiles programmatically, you may be interested in my response to the Spring integration tests with profile thread on Stack Overflow.

Comment From: spring-projects-issues

Grigory Kislin commented

This is the sample of possible fix: http://stackoverflow.com/a/33044283/548473

Comment From: spring-projects-issues

Sam Brannen commented

GKislin, indeed... that's a nice custom work-around.

Thanks for sharing!

Comment From: spring-projects-issues

Wim Deblauwe commented

Voting for this as I have this question: http://stackoverflow.com/questions/41614368

Comment From: spring-projects-issues

Hussein Yapit commented

Spring has been pretty consistent with runtime configuration (like system/env properties) trumping over compile-time config, except for this case. I argue it's a better design to allow systemProperty override @ActiveProfiles by default, i.e. without having to specify a boolean param as suggested in the description.

Alternative proposal includes make DefaultActiveProfilesResolver to be a pass-through if spring.profiles.active has been specified.

Comment From: sfussenegger

here's a simple implementation that might help with support for spring.profiles.active and spring.profiles.include:

/**
 * <p>
 * resolve active profiles from {@link ActiveProfiles#profiles() @ActiveProfiles} by default and allow overriding (using
 * {@value AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME}) or adding
 * ({@value Profiles#INCLUDE_PROFILES_PROPERTY_NAME}) profiles using {@link System#getProperty(String) system
 * properties}
 * </p>
 * 
 * <p>
 * Usage:
 * </p>
 * 
 * <pre>
 * &#64;SpringBootTest
 * &#64;ActiveProfiles(profiles = "testing", resolver = ConfigurableActiveProfilesResolver.class)
 * public class Test {
 *  // SNIP
 * }
 * </pre>
 */
public class ConfigurableActiveProfilesResolver extends DefaultActiveProfilesResolver {

    @Override
    public String[] resolve(final Class<?> testClass) {
        final String activeProperty = System.getProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(activeProperty)) {
            return split(activeProperty);
        }

        // from @ActiveProfiles
        final String[] activeProfiles = super.resolve(testClass);

        final String includeProperty = System.getProperty(Profiles.INCLUDE_PROFILES_PROPERTY_NAME);
        if (StringUtils.hasText(includeProperty)) {
            return merge(activeProfiles, split(includeProperty));
        } else {
            return activeProfiles;
        }
    }

    /**
     * @see org.springframework.core.env.AbstractEnvironment#doGetActiveProfiles()
     */
    private String[] split(final String profiles) {
        return StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles));
    }

    private String[] merge(final String[] activeProfiles, final String[] includeProfiles) {

        if (includeProfiles.length == 0) {
            return activeProfiles;
        } else if (activeProfiles.length == 0) {
            return includeProfiles;
        } else {
            // merge, ignore duplicates
            final String[] merged = new String[activeProfiles.length + includeProfiles.length];
            System.arraycopy(activeProfiles, 0, merged, 0, activeProfiles.length);
            System.arraycopy(includeProfiles, 0, merged, activeProfiles.length, includeProfiles.length);
            return merged;
        }
    }
}

Usage with unit tests:

 @SpringBootTest
 @ActiveProfiles(profiles = "testing", resolver = ConfigurableActiveProfilesResolver.class)
 public class Test {
    // SNIP
 }
 ```

Example usage from Maven

```xml
<plugin>
    <inherited>true</inherited>
    <artifactId>maven-failsafe-plugin</artifactId>
    <configuration>
        <systemPropertyVariables>
            <spring.profiles.include>mysql</spring.profiles.include>
            <spring.datasource.url>jdbc:mysql://${docker.container.mysql.ip}/test</spring.datasource.url>
            <spring.datasource.username>test</spring.datasource.username>
            <spring.datasource.password>test</spring.datasource.password>
        </systemPropertyVariables>
    </configuration>
</plugin>

Comment From: snicoll

Spring has been pretty consistent with runtime configuration (like system/env properties) trumping over compile-time config, except for this case. I argue it's a better design to allow systemProperty override @ActiveProfiles by default, i.e. without having to specify a boolean param as suggested in the description.

I am not sure about that. Being able to specify the value you want that particular test to use shouldn't be overruled by something in your environment. If we did that, then your test could fail based on external factors. IMO, if we introduce a way to tune that externally, it probably should be with its own property. Slice tests in Spring Boot use the spring.test namespace for this. Perhaps spring.test.profiles or something?

Overriding the property looks in order but I am not sure the property should be set if it wasn't

Comment From: sfussenegger

@snicoll I think with the maven-failsafe-plugin use-case there is even an argument to be made for adding something like @ActiveProfiles(profiles = "testing", profileIncludeProperty = "spring.test.profiles.include", includeProfiles = true) to allow for different profile inclusions in different (integration) tests across a test suite. For instance if I'd have MySQL- and PostgreSQL-specific code I'd like to test I might need to include "mysql" or "pgsql" accordingly. Having only a single pre-defined property would mean I'd have to configure multiple runs with different property values.

Comment From: snicoll

OK but that's a different issue altogether. We're discussing the merits of being able to change the list of profiles even if one is set in the tests using a property. You seem to be asking for something else. I didn't get the reference with failsafe nor what that code snippet would do but that looks overcomplicated to me.

Comment From: sfussenegger

@snicoll Not sure about the "we're discussing" when the last comment other than mine is from 2018 ;) If there is an ongoing discussion in a related ticket that brought you hear I'm not aware of it.

Anyway, jut like you I'd respectfully disagree with the comment by Hussein you've been quoting. I think in most projects people will run multiple tests in a single test suite. Not sure how often they'd actually want to overrides profiles for all of these tests. Maybe some would. In our projects I think we'd be running into a bunch of new issues with unwanted profile changes.

The original ticket suggested in "Implementation Notes" to "Consider introducing a new boolean flag" which is what I'd prefer and what is actually achieved by using the ConfigurableActiveProfilesResolver class I've attached in 2022. This code could therefore be merged into DefaultActiveProfilesResolver (plus handling for that new boolean flag that should be turned off by default).

My last comment was indeed an extension to that, suggesting using some kind of propertyName property rather than a hard-coded property to allow for different profiles in different tests in a single test suit run. So that would be an extra feature. Also, I'd consider supporting setting active profiles as well as profile inclusion (the old spring.profiles.include property vs spring.profiles.active). That was the includeProfiles = true in my previous comment. So either choose override or include. Again, both properties are supported in the ConfigurableActiveProfilesResolver posted above (though the constants might no longer be available in newer spring or spring-boot versions.

Comment From: snicoll

I am talking about this issue and the person who raised it. I believe I am right in assuming you're not "Alex" and that's what I mean by a different issue altogether as what you're suggesting has a far more wider scope for something we haven't even decided to consider.

spring.profiles.include is a Spring Boot specific feature, so not to be considered here. I'd like we keep the discussion to the scope as raised by the OP, at least for now.

Comment From: sfussenegger

No, I'm not Alex but I don't see how my suggestion is outside of the scope.

I think in a nutshell this is the proposal (with proposal 2 suggested to have it's own ticket, so I've ignored it)

Provide the ability to override @ActiveProfiles specified in an integration test via the spring.profiles.active system property or some other system property specific to the TCF.

Plus there's more:

Consider introducing a new boolean flag in @ActiveProfiles that allows profiles declared via the annotation to be overridden by those defined via the spring.profiles.active system property (if it is present).

You then quoted a comment by Hussein saying it shouldn't be default behaviour - if I understand correctly - and I'd agree. Therefore, assuming a new flag in @ActiveProfile it could look like this

@ActiveProfiles(profiles = "testing", profilesProperty = true)

This is equivalent to my sample code above:

// also includes spring.profiles.include support which should be removed
@ActiveProfiles(profiles = "testing", resolver = ConfigurableActiveProfilesResolver.class)

You've then suggested using a different property name. I just wanted to add, that you could even make the property name configurable to satisfy more complex use cases. So it would look like this:

// same behavior as above
@ActiveProfiles(profiles = "testing", profilesProperty = "spring.profiles.active")

Additionally, there could be another flag to indicate if profiles from the system property should be added to profiles or replace them.

// same behavior as above
@ActiveProfiles(profiles = "testing", profilesProperty = "spring.profiles.active", addPropertyProfiles = false)

Setting addPropertyProfiles = true would be what I tried to solve with spring.profiles.include in my implementation, but I agree that an obsolete spring-boot property isn't the way to go here. Yet by adding another flag (probably default false) it would still be possible to include this functionality.

To recap, I'd suggest using a property that is defined by the user rather than simply adding a boolean flag (or changing behaviour by default). Also, I'd suggest another flag to optionally switch from replacing profiles to adding them. Not sure this actually warrants a ticket of its own but if you'd like I create one.

Comment From: sbrannen

Team Decision

After further consideration, the team has decided not to set the spring.profiles.active system property automatically in DefaultActiveProfilesResolver, since that can break the configuration and runtime behavior for other test classes within the same test suite.

In addition, we will not provide built-in support for overriding profiles configured via @ActiveProfiles with a system property.

Instead, we encourage developers to rely on the standard semantics for @ActiveProfiles or implement a custom ActiveProfilesResolver (introduced in Spring Framework 4.0 in conjunction with #14972) that meets the specific needs of the project.

Various examples of custom ActiveProfilesResolver implementations have been provided in the comments in this issue, on Stack Overflow, and elsewhere.

If you need to switch the profiles in various situations, another option is to avoid the use of @ActiveProfiles altogether and instead consistently set the necessary spring.profiles.active system property or make use of profile features in Spring Boot in your build or IDE.

In light of the above, we are closing this issue.