Spring Boot Test does not use jmx property - java

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() {
// ...
}
}

Related

How can I override the Spring component scanning when running a JUnit 5 test?

I have this JUnit 5 test:
#SpringBootTest
public class MyTest {
...
}
The application default configuration loads many #Component's #Service's and #Configuration's.
For some tests, I would like to tell the Spring application running in this JUnit test context to not scan all the components and filter out some heavy loading or verbose/spammy components that might complain while running in a (mocked) environment where not all the requirements are met.
I tried to provide the #ComponentScan annotation to MyTest class in the form below. I added on purpose multiple filters and multiple types being filtered with the hope that I see less beans being loaded/registered in the application context:
#ComponentScan(
useDefaultFilters = false,
excludeFilters = {
#Filter(type = FilterType.ANNOTATION, classes = {
org.springframework.context.annotation.Configuration.class,
org.springframework.stereotype.Service.class,
org.springframework.stereotype.Component.class
}),
#Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
MyRabbitMqListener.class
})
}
)
#SpringBootTest
public class MyTest {
#Autowired
private ApplicationContext context;
#Test
public void expectingLessRegisteredBeans() {
List<String> beans = Arrays.stream(context.getBeanDefinitionNames())
// ...
.collect(Collectors.toList());
assertEquals(beans.size(), ...);
}
}
Regardless what I provide to the #CompoenntScan, I don't manage to control the amount or what beans are being scanned/registered in the JUnit 5 Spring test context.
But I still want to use the #SpringBootTest in order to get most of my application's configuration but exclude some parts of it (because they are slow, or spammy in the logs, or just throwing errors). For example, an application that receives event from inputs like Kafka, RabbitMQ, or REST, processes them and saves them to the persistence layer. I want to test the processing and persistence integration. I throw a bunch of test events to the processor #Service and then expect to see the results in the DB via the #Repository.
What would be my alternatives:
provide #SpringBootTest a whitelist of classes I want to load (not elegant, tedious)
define several Spring profiles, put condition annotations on component and activate/deactivate only the ones I need in the test code, also use different profile application-*.properties to mute some logging (the non-test code needs to be polluted with this test feature, and tedious creating multiple profiles)
other ways to build the application context from scratch (while I actually want is to use my application configuration, except some slices which are not relevant for certain tests)

Appropriate usage of TestPropertyValues in Spring Boot Tests

I came across TestPropertyValues, which is briefly mentioned in the Spring Boot docs here: https://github.com/spring-projects/spring-boot/blob/2.1.x/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc#testpropertyvalues
It's also mentioned in the Migration Guide here: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#environmenttestutils
Both examples show an environment variable to apply the properties to, but there's no other documentation that I could find.
In my tests the property setting comes too late to affect the property injection (via #Value) for a Spring Bean. In other words, I have a constructor like this:
public PhoneNumberAuthorizer(#Value("${KNOWN_PHONE_NUMBER}") String knownRawPhoneNumber) {
this.knownRawPhoneNumber = knownRawPhoneNumber;
}
Since the above constructor is called before the test code has a chance to run, there's no way change the property via TestPropertyValues in the test before it's used in the constructor.
I understand that I can use the properties parameter for #SpringBootTest, which updates the environment before beans get created, so what's the appropriate usage of TestPropertyValues?
TestPropertyValues isn't really designed with #SpringBootTest in mind. It's much more useful when you are writing tests that manually create an ApplicationContext. If you really want to use it with #SpringBootTest, it should be possible to via an ApplicationContextInitializer. Something like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(initializers = PropertyTest.MyPropertyInitializer.class)
public class PropertyTest {
#Autowired
private ApplicationContext context;
#Test
public void test() {
assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar");
}
static class MyPropertyInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("foo=bar").applyTo(applicationContext);
}
}
}
Spring Boot's own test make use of TestPropertyValues quite a bit. For example, applyToSystemProperties is very useful when you need to set system properties and you don't want them to be accidentally left after the test finishes (See EnvironmentEndpointTests for an example of that). If you search the codebase you'll find quite a few other examples of the kinds of ways it usually gets used.

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.

Cannot process locations AND classes for context configuration

I have wrote following test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:META-INF/dataContext.xml"},classes = Configiuration.class)
#ActiveProfiles("test")
public class CityDaoImplTest {
....
}
I need to use configuration from xml file and from java class bur when I invoke
mvn test I seee following in console:
Tests in error:
initializationError(***.CityDaoImplTest): Cannot process locations AND classes for context configuration [ContextConfigurationAttributes#5bb21b69 declaringClass = '***.CityDaoImplTest', classes = '{***.Configiuration}', locations = '{classpath:META-INF/dataContext.xml}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'org.springframework.test.context.ContextLoader']; configure one or the other, but not both.
How to fix it without rewriting configuration?
From the Spring Docs:
Prior to Spring 3.1, only path-based resource locations were supported. As of Spring 3.1, context loaders may choose to support either path-based or class-based resources. As of Spring 4.0.4, context loaders may choose to support path-based and class-based resources simultaneously.
However, with spring-test there is a small caveat. It uses the SmartContextLoader which is based on AbstractDelegatingSmartContextLoader and unfortunately it is not so smart ;)
#Override
public void processContextConfiguration(
final ContextConfigurationAttributes configAttributes) {
Assert.notNull(configAttributes, "configAttributes must not be null");
Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
"Cannot process locations AND classes for context "
+ "configuration %s; configure one or the other, but not both.", configAttributes));
As shown in the code, locations and classes can not both be set.
So, how to fix this? Well, one solution is to add an extra config class such as the following:
#Configuration
#ImportResource("classpath:META-INF/dataContext.xml")
class TestConfig {
}
And, in your test code use the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Configuration.class, TestConfig.class})
#ActiveProfiles("test")
public class CityDaoImplTest { ... }
Technically, this is rewriting the configuration but you do not have to alter your existing config, just add a new #Configuration class (and that class can even be in the same file as your test case).
Even if is to late for you I will post my answer just to help the other ones which will read this.
Another solution is to declare your Configuration class as bean in dataContext.xml.
All you need to do is:
<bean class="com.packageWhereConfigClassIsPresent.Configuration"/>
Hope it will help someone ;)

How to set Spring Active Profiles Pragmatically from a configuration class?

On a project I'm working on we have some old dependencies that define their own spring beans but need to be initialized from the main application. These beans are all constructed using spring profiles, i.e. "default" for production code and "test" for test code. We want to move away from using spring profiles, instead simply using #import to explicitly wire up our context.
The idea is to encapsulate all these old dependencies so that no other components need to care about spring profiles. Thus, from a test`s point of view, the application context setup can be described as follows:
#ContextConfiguration(classes = {TestContext.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
//tests
}
TestContext further directs to two classes, one of which encapsulates the old dependencies:
#Configuration
#Import(value = {OldComponents.class, NewComponents.class})
public class TestContext {
//common spring context
}
To encapsulate the old components` need for profiles, the OldComponents.class looks as follows:
#Configuration
#Import(value = {OldContext1.class, OldContext2.class})
public class OldComponents {
static {
System.setProperty("spring.profiles.active", "test");
}
}
The problem here is that the static block does not appear to be executed in time. When running mvn clean install, the test gets an IllegalStateException because the ApplicationContext could not be loaded. I have verified that the static block gets executed, but it would appear that OldContext1 and OldContext2 (which are profile dependent) are already loaded at this time, which means it is too late.
The frustrating thing is that IntelliJ runs the tests just fine this way. Maven, however, does not. Is there a way to force these profiles while keeping it encapsulated? I've tried creating an intermediary context class, but it didn't solve the problem.
If we use the annotation #ActiveProfiles on the test class, it runs just fine but this kind of defeats the purpose. Naturally, we want to achieve the same in production and this means that if we cannot encapsulate the need for profiles, it needs to be configured in the web.xml.
If your configuration classes inherits of AbstractApplicationContext you can call:
getEnvironment().setActiveProfiles("your_profile");
For example:
public class TestContext extends AnnotationConfigWebApplicationContext {
public TestContext () {
getEnvironment().setActiveProfiles("test");
refresh();
}
}
Hope it helps.
It definietly seems that OldContext1 and OldContext2 are being class-loaded and initialized before the static block in OldComponents is executed.
Whilst I can't explain why there is a difference between your IDE and Maven (to do so would require some in-depth knowledge of some, if not all all, of : spring 3.x context initialization, maven surefire plugin, SpringJunit4ClassRunner and the internal IntelliJ test runner), can I recommend to try this?
#Configuration
#Import(value = {UseTestProfile.class, OldContext1.class, OldContext2.class})
public class OldComponents {
// moved the System.setProperty call to UseTestProfile.class
}
and
#Configuration
public class UseTestProfile {
static {
System.setProperty("spring.profiles.active", "test");
}
}
If I am understanding your problem correctly, class UseTestProfile should be loaded first (you might want to investigate a way to guarantee this?) and the other two classes in the import list should have the system setting they need to initialize properly.
Hope this helps...
You need make sure, environment takes effect at first.This is how I do:
#Component
public class ScheduledIni {
#Autowired
private Environment env;
#PostConstruct
public void inilizetion() {
String mechineName = env.getProperty("MACHINE_NAME");
if ("test".equals(mechineName) || "production".equals(mechineName) {
System.setProperty("spring.profiles.default", "Scheduled");
System.setProperty("spring.profiles.active", "Scheduled");
}
}
}
In scheduler add annotation Prodile and DependsOn to make it work.
#DependsOn("scheduledIni")
#Profile(value = { "Scheduled" })
#Component
Use #profile annotation in the class to load the configuration like below
#Configuration
#Profile("test")
public class UseTestProfile {
}
and set the value for the property spring.profiles.active either in property file or as a runtime argument

Categories

Resources