10

So confusingly @IfProfileValue has nothing to do with @Profile or @ActiveProfiles. @Profile tests to see if a profile is active, @ActiveProfiles sets them as active, and @IfProfileValue allows you to check things in Spring Environment. Wut? I'd deprecate all of them and add new ones @IfEnvironment, @IfProfile, and @ActivateProfiles.

Commentary aside, how can I use @IfProfileValue to detect whether i have a profile active? I am not using Spring Boot on this project, at this time. Answers should show code, and we will assume that I want the test to run if the profile is activated as @ActiveProfiles( "test" ).

I tried @IfProfileValue(name = "activeProfiles", value = "test") but that seems to have the test skipped, which means it's not matching. I'm going to speculate the problem may have to do with the fact that ActiveProfiles is a Collection.

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
xenoterracide
  • 16,274
  • 24
  • 118
  • 243

4 Answers4

26

So confusingly @IfProfileValue has nothing to do with @Profile or @ActiveProfiles.

That's correct, and I explained this in detail here: https://stackoverflow.com/a/23627479/388980

... which I'm assuming you have already seen, since you commented on my answer yesterday.

The reason that @IfProfileValue has nothing to do with @Profile or @ActiveProfiles is due to the evolution of the framework. See below for further details.

@Profile tests to see if a profile is active, @ActiveProfiles sets them as active, and @IfProfileValue allows you to check things in Spring Environment.

These statements are not entirely correct, especially the last part.

@Profile is used to selectively enable a component (e.g., @Service, etc.), @Configuration class, or @Bean method if one of the named bean definition profiles is active in the Spring Environment for the ApplicationContext. This annotation is not directly related to testing: @Profile should not be used on a test class.

@ActiveProfiles is used to designate which bean definition profiles (e.g., those declared via @Profile) should be active when loading an ApplicationContext for an integration test.

@IfProfileValue does not allow you to check things in the Spring Environment. I'm not sure why you are assuming this, since none of the documentation in the Spring Framework states that. As I stated in the aforementioned thread:

Please note that @IfProfileValue was introduced in Spring Framework 2.0, long before the notion of bean definition profiles, and @ActiveProfiles was first introduced in Spring Framework 3.1.

In the aforementioned thread, I also pointed out the following:

The term 'profile' is perhaps misleading when considering the semantics for @IfProfileValue. The key is to think about 'test groups' (like those in TestNG) instead of 'profiles'. See the examples in the JavaDoc for @IfProfileValue.

how can I use @IfProfileValue to detect whether i have a profile active?

That depends, and... I'm assuming you mean bean definition profile when you say "profile".

If you're using @ActiveProfiles to set the bean definition profiles for your tests, you cannot currently use @IfProfileValue to determine if a bean definition profile is active, since the bean definition profiles configured via @ActiveProfiles are set directly in the test's ApplicationContext and not as a Java system property.

However, if you are setting the bean definition profiles only via the spring.profiles.active system property, then it would be possible to use @IfProfileValue to determine if a bean definition profile is active, since @IfProfileValue in fact works with system properties. For example, you could then use the following:

@IfProfileValue(name = "spring.profiles.active", value = "test")

I tried @IfProfileValue(name = "activeProfiles", value = "test") but that seems to have the test skipped, which means it's not matching.

That won't work since activeProfiles is the incorrect property name. The correct system property name is spring.profiles.active. See AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME for details.


The fact that @IfProfileValue does not work in harmony with @ActiveProfiles is a known issue to the Spring team. Please consult the following JIRA issues for further details and to join in on the discussions if you'd like.

Hope this clarifies the situation for you!

Sam (author of the Spring TestContext Framework)

Community
  • 1
  • 1
Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • I think I got the "environment" idea from one of the jira/SO threads... I also may have assumed that it was using the property source merger thing, which, afaik, would allow access to the environment as well. My mistake. I get that there are historical reasons for the naming differences, which is why I would deprecate them all in v5, and give them new names. I find all of the annotations to have uses that aren't obvious by looking at the code that is using them. – xenoterracide Oct 11 '15 at 01:55
  • 2
    One feedback is that if you have more than one active profile then checking the `spring.profiles.active` against a specific profile would fail as there is multiple, comma separated, profile names. – Shawn Clark Aug 20 '16 at 00:16
  • Could you point out why `@Profile` should not be used on a test class? Please see https://stackoverflow.com/q/53313489/4106030 – badera Nov 15 '18 at 06:18
6

Sam nailed it. (As well as the fact this was accepted and answered years back)

One thing to add is that if you'd like to pass System Properties through to your test, having them propagate through to the JVM if you are using a build tool like gradle may require an additional step.

//build.gradle
tasks.withType(Test) {
    systemProperties System.properties
}

And then business as usual in your integration test

//MyIntegrationTest.class
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("integration")
@IfProfileValue(name = "run.integration.tests", value = "true")
public class MyIntegrationTest {
    @Test
    public void myTest(){ ... }
}

Finally you can execute your tests from the terminal with the property you specified.

$> ./gradlew clean test -Drun.integration.tests=true

The thing I like most about @IfProfileValue over grabbing the System.property and checking assumeTrue/False manually is that no Spring Context is loaded (or flyway/other migrations you may have) keeping unit tests fast.

mcnichol
  • 175
  • 2
  • 14
  • That all being said, I realize I injected "integration testing" here and that wasn't really mentioned in OP. – mcnichol Nov 29 '17 at 05:27
  • eh, we're using it for integration testing with upstream API providers, to avoid abusing their API every time a test runs. – xenoterracide Dec 01 '17 at 16:35
2

Unfortunately, from my experience, test dependency on @IfProfileValue

@Test
@IfProfileValue(name="spring.profiles.active", values={"test"})

Will work only when you set spring.profiles.active as a JVM property, as: -Dspring.profiles.active="test"

@IfProfileValue just ignores spring.profiles.active from application.properties/yml.
ylev
  • 2,313
  • 1
  • 23
  • 16
  • Confirmed. Another important node: the ['OR' Semantics](https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#integration-testing-annotations-junit4-ifprofilevalue) of `@IfProfileValue` must be used when testing property `name="spring.profiles.active"`. So `@IfProfileValue(name="spring.profiles.active", values={"test"})` works but `@IfProfileValue(name="spring.profiles.active", value="test")` will not. – brianNotBob Aug 05 '19 at 22:00
  • For junit5, the following annotation may be necessary instead: https://stackoverflow.com/a/53718280/2695203 – Kyle Dunn Oct 28 '20 at 00:35
0

When using junit5 it is possible to conditionally enable the test based on the active profile:

@EnabledIf(expression =
   "#{environment.acceptsProfiles(T(org.springframework.core.env.Profiles).of('someprofile'))}",
   loadContext = true)
class MyTest {
   ...
}

This checks if someprofile is active and unlike checking for spring.profiles.active property works event if there are more than one profile enabled. This checks if someprofile is among currently active profiles.

In order to make this more readable it is possible to use spring meta-annotation. Define annotation first

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@EnabledIf(expression =
  "#{environment.acceptsProfiles(T(org.springframework.core.env.Profiles).of('someprofile'))}",
  loadContext = true)
public @interface EnabledIfSomeProfile {
}

And then use it on test classes:

@EnableIfSomeProfile
class MyTest {
 ...
}