How can I use #IfProfileValue to test if a Profile is active? - java

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.

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.
https://jira.spring.io/browse/SPR-7754
https://jira.spring.io/browse/SPR-8982
https://jira.spring.io/browse/SPR-11677
Hope this clarifies the situation for you!
Sam (author of the Spring TestContext Framework)

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.

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.

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 {
...
}

Related

Can You Enable Bean Overriding On A Single Test Class?

One of my test classes has conflicts between the test beans and the production beans.
I can fix that by adding spring.main.allow-bean-definition-overriding=true to my test application.properties.
However, this now applies to all the tests.
Is it possible to apply this to a single test?
e.g. with an annotation?
Yes, you can set properties in the SpringBootTest annotation.
#SpringBootTest(properties =
{ "spring.main.allow-bean-definition-overriding = true" })
class TestClass{}

Spring Initialisation Order with Configuration

I have a Spring project, and I need to configure Flyway.
Using the default FlywayAutoConfiguration, Flyway migration is immediatly executed before everything else (caching, PostConstruct annotations, services). This is the behavior I expected (in term or startup workflow)
Unfortunatly, I need to override the default FlywayAutoConfiguration because I use a custom Flyway implementation, but that's not the my main problem here, my issue is really related to Spring Priority for configuration and initialization sequence.
So do use my own flyway, I first copied FlywayAutoConfiguration to my maven module, name it CustomFlywayAutoConfiguration and just adapt imports. I also change the property "spring.flyway.enabled" to false and create another one "spring.flywaycustom.enabled" to be able to activate mine and not the default one.
Doing that fully change the sequence of startup. Now, flyway is executed at the end of the startup sequence (after caching and other #PostConstruct that are in my project)
The following bean defined in CustomFlywayAutoConfiguration is now created onyl at the end of the startup sequence. With the default FlywayAutoConfiguration , was well created on the beginning.
#Bean
#ConditionalOnMissingBean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
I tryied to play a lot with ordering (HIGHEST_PRECEDENCE, and LOWEST_PRECEDENCE)
#AutoConfigureOrder on configuration class
#Order on components
Try to Autowire FlywayMigrationInitializer to force the bean initialisation earlier
It's doesn't change anything, looks like Spring ignore #Order and #AutoConfigureOrder
Any idea why when Configuration is inside spring-boot-autoconfigure dependencies, it started as first priority, and when the same Configuration code is inside my project, I don't have the same ordering?
Thank you so much for your help.
Thanks to your answer, Focusin my attention FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor help me to solve the issue.
#Configuration(proxyBeanMethods = false)
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
#Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class }) // Very important to force flyway before
public class CustomFlywayConfiguration {
#Bean
public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
#FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
DataSource dataSourceToMigrate = configureDataSource(configuration, properties, dataSourceProperties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
checkLocationExists(dataSourceToMigrate, properties, resourceLoader);
configureProperties(configuration, properties);
List<Callback> orderedCallbacks = callbacks.orderedStream().collect(Collectors.toList());
configureCallbacks(configuration, orderedCallbacks);
fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
configureFlywayCallbacks(configuration, orderedCallbacks);
List<JavaMigration> migrations = javaMigrations.stream().collect(Collectors.toList());
configureJavaMigrations(configuration, migrations);
return new CustomFlyway(configuration);
}
/**
* FlywayAutoConfiguration.FlywayConfiguration is conditioned to the missing flyway bean. #Import annotation are not executed in this case.
*
* So if we declare our own Flyway bean, we also need to create this bean to trigger the flyway migration.
*
* The main issue is now that bean is create after the #PostConstruct init method in MyInitComponent.
*
*/
#Bean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
/**
* Thanks to that, it's now working, because make sure it's required before to create EntityManager
*/
// #ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
// #ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
...
So that confirm we can't easily override the Flyway bean without copy some logic from FlywayAutoConfiguration.
I created a small project to reproduce the bug, with the solution I found.
https://github.com/w3blogfr/flyway-issue-demo
I don't know if it's necessary to fix it or not in spring auto-configuration project?
I checked the history to find back the commit
https://github.com/spring-projects/spring-boot/commit/795303d6676c163af899e87364846d9763055cf8
And the ticket was this one.
https://github.com/spring-projects/spring-boot/issues/18382
The change looks technical and probably he missed that was an #ConditionalOnClass
None of the annotations that you've described have an impact on runtime semantics:
#AutoConfigureOrder works only on auto-configurations (so if you've copied the auto-configuration as a user config, it won't even be considered). This is used to order how auto-configurations are parsed (typical use case: make sure an auto-config contribute a bean definition of type X before another one check if a bean X is available).
#Order orders components of the same type. Doesn't have any effect as to "when" something occurs
Forcing initialisation using an injection point is a good idea but it'll only work if the component you're injecting it into is initialised itself at the right time.
The auto-configuration has a bunch of post-processors that create links between various components that use the DataSource and the FlywayMigrationInitializer. For instance, FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor makes sure that FlywayMigrationIntializer is a dependency of the entity manager so that the database is migrated before the EntityManager is made available to other components. That link is what's making sure Flyway is executed at the right time. I don't know why that's not working with your copy, a sample we can run ourselves shared on GitHub could help us figure out.
With all that said, please don't copy an auto-configuration in your own project. I'd advise to describe your use case in more details and open an issue so that we consider improving the auto-configuration.

SpringBootTest - how to assert context not loads and change properties at test level?

I rely on #SpringBootTest heavily when testing application configuration. App properties can be complex, having default values and non-trivial validations. For example:
prop:
ports: 1205,2303,4039
fqdn: ${server.host}:${ports[0]}/${path}
#Configuration
SomeConfigClass{
#Value{"${ports}:{#collections.emptyList}"}
List<Integer> ports;
...
}
When testing such apps, I bring up a full or partial application context without mocks, as there is complexity in the context and validation itself - mocks don't capture this. Unfortunately, there are two limitations I keep finding with this pattern:
How can we test that bad configurations fail to load?
Imagine testing that the port in invalid because it is not on the restricted range of 500 - 1500.
#SpringBootTest(
classes = {SomeConfigClass.class},
properties = "port=9000"
)
public class BadConfigTest{
#Test(expected = ApplicationContextFailedException.class)
public void WHEN_port_9000_THEN_appcontext_fails_to_load() {}
}
Since the test framework loads after the application context, there appears to be no way to test that an app context fails to load. For now I actually write the tests, manually confirm they fail, and then annotation with #Ignored so they are not lost.
How to change properties at the test method, rather than class, level?
#SpringBootTest is a class annotation, meaning application properties are bound at the test-class level. This results in needing a test class for many sets of properties and bloats the test suite. For example, I'll end up with test classes like:
ConfigPropertiesAllValidTest
ConfigPropertiesNoneSetTest
ConfigPropertiesSomeValidSomeNotTest
Where each of these only has one or two test cases. Preferably, there'd be a single ConfigPropertiesTest class with different props for each test. Is this possible?
Again - I want to avoid mocks as they don't capture the non-trivial context autoconfiguration performed by Spring at runtime.
We ended up using the ApplicationContextRunner described in this document:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
You can use the #ActiveProfiles annotation along with the #SpringBootTest annotation to load properties for different profiles. This is class level, so will only help for case 1 of your question.
#SpringBootTest(classes = {SomeConfigClass.class})
#ActiveProfiles("badconfigtest")
public class BadConfigTest{
...
}
Then have an application-badconfigtest.properties with your bad config.
I don't think you'll find a way of changing properties between test methods in the same class. You can use #DirtiesContext to reload the application context, but I've not seen a way to use different property files. I guess you could inject the values into the config classes that have already loaded the properties.

Spring Boot Test does not use jmx property

I have a test clas with
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class Foo{
...
}
which should start up a regular application context as defined by:
#SpringBootApplication(scanBasePackages = {"de.foo", "de.bar"})
public class Application {
...
}
This works as expected. Further I have an application.yml which gets loaded in both cases but when running the test, the property for JMX (spring.jmx.enabled) does not get loaded or it does not get used.
I tried different property files (application.yml, application-test.yml) but the only thing what works is setting the property via
#TestPropertySource(properties = "spring.jmx.enabled:true")
The property defaults to true in a regular application context.
Several questions:
Why is the default different in a test class?
Why does the property not get loaded or recognized, when loading it from an application.yml (the rest of the yml works, so it does get loaded).
This seems to be a known behavior, as seen in this comment in Spring Boot Sample Data Tests. Is there any documentation I missed about this behavior?
I've recently encountered the same situation myself, and have opened spring-projects/spring-boot#13008 to document this behavior. As a result, the following additions to the reference manual will be added in the upcoming 1.5.13.RELEASE and 2.0.2.RELEASE:
As the test context framework caches context, JMX is disabled by default to prevent identical components to register on the same domain. If such test needs access to an MBeanServer, consider marking it dirty as well:
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "spring.jmx.enabled=true")
#DirtiesContext
public class SampleJmxTests {
#Autowired
private MBeanServer mBeanServer;
#Test
public void exampleTest() {
// ...
}
}

What is the equivalent of #DataJpaTest if I just want to test JdbcTemplate code?

Spring Boot 1.4 offers some fantastic testing improvements. One is the #DataJpaTest annotation where it wires up just the parts needed for JPA testing. What would the equivalent look like for just wiring up the parts needed for JdbcTemplate tests?
I'm fine constructing my own composite annotation that mimics the #DataJpaTest one.
Good question. Ironically enough, that one was raised during the testing talk yesterday at SpringOne Platform. Let's see what it takes to implement such dedicated test annotation.
TL;DR check the code on github
First of all you need to create the annotation. This annotation reuses some bits from the spring-boot-test-autoconfigure module. You may want to auto-configure an in-memory database (like DataJpaTest does). You also want to make sure that caching is configured and disabled by default (in case you have #EnableCaching on your Spring Boot application). You also want that all your tests are #Transactional by default so you should add that.
Next, you want that slicing effectively kicks in. All you need at this point is a DataSource, a JdbcTemplate, database migrations (flyway/liquibase) and a transaction manager to process #Transactional. To avoid the other auto-configurations to kick in you should add the following:
#OverrideAutoConfiguration(enabled = false)
Then, you want to explicitly enable the auto-configurations above. In order to do so, you add #ImportAutoConfiguration and you add the following content in META-INF/spring.factories
# AutoConfigureDataJpa auto-configuration imports
com.example.test.autoconfigure.jdbc.DataJdbcTest=\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
The key in spring.factories should match the FQN of your annotation. Whenever Spring Boot finds #ImportAutoConfiguration with no extra attributes, it will look for a key matching the annotation type in spring.factories.
Next up you want to be able to include additional components (component scan) with a filter. In order to do that, you can add #TypeExcludeFilters(DataJdbcTypeExcludeFilter.class) where DataJdbcTypeExcludeFilter is pretty much the same thing as DataJpaTypeExcludeFilter (so we might want to extract a common class for that).
Once you've done that, you only need to add your annotation and your JdbcTemplate is auto-configured for you
#RunWith(SpringRunner.class)
#DataJdbcTest
public class DataJdbcSampleTests {
#Autowired
private JdbcTemplate jdbcTemplate;
...
}
I think the option will be #JdbcTest, you could found further info on doc.

Categories

Resources