I've a legacy code as below
#Service
public class CLConf {
static final String CI_ENV = System.getenv("ENV").toUpperCase(); <-- NPE when doing junits
// other config variables
}
This works when we run the code normally in application. but I've issues creating test cases that use class CLConf something like below.
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes = CLConf.class) <<----------- ISSUE while loading bcz env is not set yet
public class CLServiceTest {
//test cases
}
currently it throws NPE because of .toUpperCase()
Since CI_ENV is directly being set from System.getenv, while writing class for Junits, this class is loaded using #ContextConfiguration. And I'm not sure where to set env variables for Test cases so that it doesn't break while loading CLConf.
How do I configure/do setup for test cases OR how do i decouple System.getenv so that I can supply my own config while testing?
Note: there are many System.getenv in class CLConf, above is just minified class.
You can provide test configurations using the locations or value attribute of the TestPropertySource annotation, directly defining your test properties in a file or selectly override properties like stated in the documentation:
#TestPropertySource is a class-level annotation that is used to
configure the locations() of properties files and inlined properties()
to be added to the Environment's set of PropertySources for an
ApplicationContext for integration tests.
Test property sources have higher precedence than those loaded from
the operating system's environment or Java system properties as well
as property sources added by the application declaratively via
#PropertySource or programmatically (e.g., via an
ApplicationContextInitializer or some other means). Thus, test
property sources can be used to selectively override properties
defined in system and application property sources
Related
We are loading properties from an external file using #PropertySources. Now I want to enable/disable #Aspect based on a property. I tried using #ConditionalOnExpression which didn't work. I tried the same by creating a bean of propertyplaceholderconfig. Even in the same case, it didn't work. Then I tried #profile which also didn't work initially.
What I Figured out is that these variables are not initialized at the starting when propertysource or propertyplaceholder bean is used at startup. Some variables are always ignored like (logging.file). But #Value works fine. In order to set these variables, I've to pass them as JVM parameters.
So my questions are:
1. How can I make spring to always read specified property files at startup and respect all of them?
2. Which is the best way to enable/disable #Aspect. Using #profile or #ConditionalOnExpression or something else?
Currently, we are setting logging.file in the main method since this also behaves the same way. But you guys know that it's not the proper way as I may end up adding the properties one by one like this. I want to put all the properties into external files such that spring reads those files and sets its properties.
Our properties structure:
common.properties #This has all common properties
service.properties #Property specific to a service. This will also contain existing property from common.properties which will be overridden.
I understand that I can use profiles. But, we want to keep the properties outside such you need to restart service if you are changing the properties. I also don't want to pass the variables as JVM parameters then I've to pass most of the variables in this way. Passing -Dspring.config.location is also difficult as common.properties and service.properties are used and 'service.properties' filename varies for each service.
sample codes:
Mainclass:
#PropertySources({
#PropertySource(value = "file:${property_path}/common.properties", ignoreResourceNotFound = false),
#PropertySource(value = "file:${property_path}/service1.properties", ignoreResourceNotFound = true) })
public class MainClass {
static String logDirectory = ApplicationContext.getGlobalProperty("logging.file");
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainClass.class);
Properties properties = new Properties();
properties.put("logging.file", logDirectory);
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
Application Context:
#Configuration
#EnableAutoConfiguration
public class ApplicationContext implements EnvironmentAware {
private static Environment environment;
#Override
public void setEnvironment(Environment environment) {
ApplicationContext.environment = environment;
}
public static String getGlobalProperty(String propertyName) {
return environment.getProperty(propertyName);
}
}
Here you can see any way I've used environment to get property. Is there any way to set the property using the environment such that while spring boot initialization itself the properties are populated?
We can also implement ApplicationContextInitializer and override initialize method to read properties. But how can I make it read 2 property files and override the duplicate property with the latest value? Reference(I'm not sure how to implement my requirements in this way.). Even in this case doesn't sound like you are trying to kill a mosquito with a hammer?
Current working Solution:
#Aspect
#Profile("!production")
#Configuration
public class ControllerAspect {
#pointcut(....)
} //Here also I've to pass spring.profiles.active as JVM params.
//setting the value in common.properties or service1.properties is not working.
I'm a newbie to spring boot so please let me know for additional clarifications.
It seems Spring by default loads some properties at initialization and unless until you specifically write logic to overwrite them (like the one I wrote in MainClass.java) there is no option to override those. Some of these include (logging.file, key used in #ConditionalonExpression).
Some tricks with their own challenges:
Specify the properties in application.properties in your classpath. The variables loaded at the earlier stages are always read from this file. challenge: I've tight coupled all my properties into the jar and in order to change the values I've to recompile and relaunch the Jar.
Use profiles and define application.properties as application-profile.properties. challenge: I've to create so many profiles and still the previous challenge exists.
Pass the property value as JVM parameter as -Dproperty.key=value. challenge:seriously? How many properties am I supposed to send as JVM parameter?
Implement ApplicationContextInitialize and override initialize method.challenge:Overriding Spring's default behaviour is not recommended as well as isn't it an overkill to use this just for reading property file?
Solution:
Use -Dspring.config.location to specify the property files. In this case, always spring reads the properties only from the specified location(s). You can provide multiple property files as well. Refer this for much more details. It seems if you give property locations as Directories spring loads them in reverse order. But if you specify files it follows the order specified.
Note: All these can be combined together. To know about precedence refer this.
I have many different services using spring-boot. I'd like to set up some configuration that is common for each, but allow the services to have their own properties and override them if they want. Example properties include spring.show_banner, management url ones, etc.
How can I do this? If I have the following:
service-common with src/main/resources/application.yml with default properties
service1 with src/main/resources/application.yml with its own properties
I'd like them to be merged with the service1 version taking precedence. Instead, it seems that only the first one found on the classpath is used.
(Alternatively, using #Configuration classes would be even better, but I'm not sure they can be used to define many of the properties)
There are several options available to you, all based on the order in which property sources are considered.
If your common library is responsible for creating the SpringApplication it can use setDefaultProperties. These values can be overridden by your services' application.properties.
Alternatively, your library could use #PropertySource on one of its #Configuration classes to configure, for example, library.properties as a source. Again, these properties could then be overriden in your services' application.properties.
I am not sure what you mean by merging them.
But I'm assuming that in the end, you are describing the situation where you have profile-specific configuration. Because, any properties that are specific to a certain service can be managed/injected using Spring profiles, which will always take precedence over default property files (see documentation).
For example, you can have the file application-service1.properties which would automatically be used when you run your app with the property spring.profiles.active=service1, which can be specified in the command line and other places.
If you don't specify this property, Spring Boot will fallback to the default application.properties file.
And you can of course write the common properties in both files:
application.properties:
service.url=http://localhost:8080/endpoint
service.user=admin
service.password=admin
application-service1.properties:
service.url=http://api.service.com/endpoint
service.user=admin
service.password=aosdnoni3
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
//set props as desired
env.getPropertySources()
.addFirst(new PropertiesPropertySource("customname", props));
}
}
Then in src/main/resources/META-INF/spring.factories, add line:
org.springframework.context.ApplicationListener=mypackage.MyApplicationListener
I am currently polishing a test framework we have. For the current needs, we must support multiple spring profiles, and run our tests multiple times, each time with a different profile. Each profile targets separate test environments, and thus different sets of tests, with different logic may be executed.
I am having a test class like this:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
public class Test extends AbstractTestNGSpringContextTests {
#Autowired
ProfileSpeciticBean profileSpecificBean;
...
}
Here, ProfileSpecificBean is an interface, that is implemented by separate classes. The actual implementation to be injected is determined by the active Spring profile, and I am using Spring XML contexts. I am building the project with Maven, using the -Dspring.profiles.active=profileName command, thus expecting the tests to catch the passed profile.
However, the current test fails with this error within the full stacktrace:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ProfileSpeciticBean found for dependency: expected at least 1 bean which qualifies as autowire candindate, found 0
After some reaearch on this topic, I found that the AbstractTestNGSpringContextTests expects an #ActiveProfiles annotation on top of the test class. So, this code works:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
#ActiveProfiles("profile1")
public class Test extends AbstractTestNGSpringContextTests ...
The problem with this is: I want to avoid hard-coding the profile name in my classes. I need to run the same test class for different profiles, by only altering the command-line script.
Is the above possible? Is there any way to make TestNG aware of the command-line profile, and re-use the same test? I need to avoid both duplicating code and configuration to make my tests run, so making two Test classes for each profile is not what I want.
Try following How to set JVM parameters for Junit Unit Tests? to set the system variables for the VM that actually runs the tests - it's not the same one as the one that runs maven.
Set your profile there.
You can use a maven system parameter to set that from the invocation of maven (or use maven profiles).
To get more precise answer I suggest you add stacktrace and the piece of you main configuration (where you declared beans that is supposed to be replaced by test beans).
Here is the general idea:
let's say you want to change PropertyPlaceholderConfigurer depending on your profile.
Steps:
You create you main-config.xml
that contains PropertyPlaceholderConfigurer and mark it with profile="default"
You create you test-config.xml with test implementation of PropertyPlaceholderConfigurer
(don't forget to mark test-config.xml with profile="MyTestProfile" or mark only PropertyPlaceholderConfigurer with profile="MyTestProfile)
Than you import both test-config.xml and main-config.xml to your tests
#ContextConfiguration(locations = { "classpath:META-INF/main-config.xml","classpath:META-INF/test-config.xml" })
#ActiveProfiles("MyTestProfile")
public class Test extends AbstractTestNGSpringContextTests {}
It should work. Good luck.
I'm in the process of moving all of my Spring Configurations to Java code. I've run into a problem where I now want to set which profile I am using based on a command line switch or maven profile, etc... I also want to avoid having to place all of the same annotations on each of my test classes. This is not a web application, but rather a functional test suite.
Here is my attempt:
public class CompanyApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
final AnnotationConfigApplicationContext rootContext = new AnnotationConfigApplicationContext();
rootContext.getEnvironment().setActiveProfiles(System.getProperty("spring.profile.active", "local"));
rootContext.register(LocalConfiguration.class, SauceLabsConfiguration.class);
}
}
Then I have my tests annotated with the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = CompanyApplicationContextInitializer.class)
However when I attempt to run my tests, my autowired pieces are not being located. Am I on the right track at all? How can I wire in this class to programatically set my ApplicationContext?
The problem with your example above is that you're passing an ApplicationContextInitializer class #ContextConfiguration#classes. The #classes attribute is intended to accept classes marked with Spring's #Configuration annotation.
ApplicationContextInitializer is intended for use primarily in web applications, where it is difficult to get programmatic access to the WebApplicationContext. The "contextInitializerClasses" init-param can be passed to the Spring DispatcherServlet, and Spring will call your ACI implementation at the right time, allowing you to manipulate the application context prior to #refresh().
In your case, it appears you are concerned only with activating profiles for an integration test. So your ACI is unnecessary. Mark your integration test with Spring's #ActiveProfiles annotation to dictate which profiles are active.
Note that if spring.profiles.active has been set as a JVM system property or environment variable, the specified profile(s) will be activated automatically. i.e. there is no need to call System#getProperty as you do in your ACI implementation. One thing to note, however, is that based on the logic in your ACI implementation, it appears you want to fall back to a profile named "local" if spring.profiles.active is note supplied as a system property or environment variable. You may be interested to know that there is a "reserved default profile" named literally "default". This probably has the same semantics you're looking for with your "local" profile. Consider renaming your 'local' profile to 'default'.
Finally, note that there does exist an open improvement request for providing ApplicationContextInitializer support in #ContextConfiguration classes: https://jira.springsource.org/browse/SPR-9011. You might want to put a watch on that. It would, for example, allow you a simple option for programmatically activating 'local' if no other profiles are active.
Try adding the locations of your app context XML to the second annotation:
#ContextConfiguration(locations = {
"classpath:applicationContext.xml"
})
I recently found a solution that allows me to load system properties for my unit tests. It works great if I'm running a test individually, but if I choose to run the whole test suite, it fails. Can someone tell me why?
The first step is to load the test application context:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "/applicationContext-test.xml")
The next step is to create a class which will load the system properties:
import java.io.InputStream;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.springframework.core.io.Resource;
public class SystemPropertiesLoader{
private Resource resource;
public void setResource(final Resource resource){
this.resource = resource;
}
#PostConstruct
public void applyProperties() throws Exception{
final Properties systemProperties = System.getProperties();
final InputStream inputStream = resource.getInputStream();
try{
systemProperties.load(inputStream);
} finally{
inputStream.close();
}
}
}
The final step is to list this as a bean in my test application context:
<bean class="com.foo.SystemPropertiesLoader">
<property name="resource" value="classpath:localdevelopment_Company.properties" />
</bean>
When I run the test suite, several of my tests, all of which rely on system properties, fail. If I go to the specific test and run it, it will pass. I've debugged it and I've verified that the code in SystemPropertiesLoader is being executed, and all other beans are being pulled successfully from the context. However, the properties are not being loaded correctly, as they are all coming up null when I try to access them. Any suggestions?
A few ideas:
If you are unit testing, so why not set the required properties in each individual test case. There is no point using spring to set a global variable.
Why do you use system properties. Spring manages property objects that you can inject into you beans. They can be setup in the appContext.xml and also be initialised there (see: PropertyPlaceHolderConfigurer) using System properties. Having your code access System properties is against the very philosophy of spring.
Setting system properties from a file is rather wrong anyways. Normally you would use System properties to override settings in the properties file.
The problem was actually that the values from the Properties class were defined statically. So here's the case that broke the solution:
Test A is run. Test A does not load applicationContext-test.xml but it does call into code that uses values from the Properties class.
Now, all values from the Properties class are defined permanently.
Test B is run. Test B loads applicationContext-test.xml.
The SystemPropertiesLoader is run, loading values into system properties.
A value is retrieved from the Properties class, but since they were defined statically and assigned previously, the values from system properties never get in there.
In the end, the best solution was to define default values within the Properties class.
Could it be possible that each of your test cases is spawning a new JVM, and the System properties are not being set for each test case?
Maybe try to leverage the setUp() and tearDown() methods in your JUnit test class.