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
value
orprofiles
attribute of@ActiveProfiles
- since Spring Framework 3.1 - Specifying a custom
ActiveProfilesResolver
(which programmatically determines the active profiles) declared via theresolver
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
- Provide the ability to override
@ActiveProfiles
specified in an integration test via thespring.profiles.active
system property or some other system property specific to the TCF. - Set the
spring.profiles.active
system 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
boolean
flag in@ActiveProfiles
that allows profiles declared via the annotation to be overridden by those defined via thespring.profiles.active
system 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
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
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>
* @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
```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 thespring.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 thespring.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.