spring-framework
spring-framework copied to clipboard
Allow overriding @ActiveProfiles in test classes with system property [SPR-8982]
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:
- Providing a hard-coded list of profiles declared via the
valueorprofilesattribute of@ActiveProfiles- since Spring Framework 3.1 - Specifying a custom
ActiveProfilesResolver(which programmatically determines the active profiles) declared via theresolverattribute 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
- Provide the ability to override
@ActiveProfilesspecified in an integration test via thespring.profiles.activesystem property or some other system property specific to the TCF. - Set the
spring.profiles.activesystem property to the value specified via@ActiveProfiles- See comments in this issue for background.
- This point likely warrants its own JIRA issue for proper consideration.
Implementation Notes
- Consider introducing a new
booleanflag in@ActiveProfilesthat allows profiles declared via the annotation to be overridden by those defined via thespring.profiles.activesystem property (if it is present).
Further Resources
Related discussions at Stack Overflow:
- Spring integration tests with profile
- Defining a spring active profile within a test use case
- @ActiveProfiles value is not being assigned to config
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
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...
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.
Sam Brannen commented
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
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
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. ;)
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
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.
Grigory Kislin commented
This is the sample of possible fix: http://stackoverflow.com/a/33044283/548473
Wim Deblauwe commented
Voting for this as I have this question: http://stackoverflow.com/questions/41614368
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.
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>
* @SpringBootTest
* @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
<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>
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
@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.
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.
@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.
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.
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
@ActiveProfilesspecified in an integration test via thespring.profiles.activesystem property or some other system property specific to the TCF.
Plus there's more:
Consider introducing a new
booleanflag in@ActiveProfilesthat allows profiles declared via the annotation to be overridden by those defined via thespring.profiles.activesystem 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.
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.