Spring-boot utilizes Spring profiles which allows to have separate config for different environments.
One way I use this feature is to configure the test database to be used by integration tests.
I wonder, however: is it necessary to create my own profile 'test' and explicitly activate this profile in each test file?
Right now, I do it in the following way:
Create application-test.properties inside src/main/resources
Write test-specific config there (just the database name for now)
In every test file, include:
#ActiveProfiles("test")
Is there a smarter / more concise way? For instance, a default test profile?
Edit 1: This question pertains to Spring-Boot 1.4.1
As far as I know there is nothing directly addressing your request - but I can suggest a proposal that could help:
You could use your own test annotation that is a meta annotation comprising #SpringBootTest and #ActiveProfiles("test"). So you still need the dedicated profile but avoid scattering the profile definition across all your test.
This annotation will default to the profile test and you can override the profile using the meta annotation.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#SpringBootTest
#ActiveProfiles
public #interface MyApplicationTest {
#AliasFor(annotation = ActiveProfiles.class, attribute = "profiles") String[] activeProfiles() default {"test"};
}
Another way to do this is to define a base (abstract) test class that your actual test classes will extend :
#RunWith(SpringRunner.class)
#SpringBootTest()
#ActiveProfiles("staging")
public abstract class BaseIntegrationTest {
}
Concrete test :
public class SampleSearchServiceTest extends BaseIntegrationTest{
#Inject
private SampleSearchService service;
#Test
public void shouldInjectService(){
assertThat(this.service).isNotNull();
}
}
This allows you to extract more than just the #ActiveProfiles annotation. You could also imagine more specialised base classes for different kinds of integration tests, e.g. data access layer vs service layer, or for functional specialties (common #Before or #After methods etc).
You could put an application.properties file in your test/resources folder. There you set
spring.profiles.active=test
This is kind of a default test profile while running tests.
There are two approaches.
Load from config/
(2022 update, tested against Spring Boot 2.6)
Along with the approach below, you can also add config to src/test/resources/config/application.yml
src/
├── main/
│ ├── java/
│ │ └── ...
│ └── resources/
│ └── application.yml <- default properties, always loaded
└── test/
├── java/
│ └── ...
└── resources/
└── config/
└── application.yml <- test properties, will override the defaults
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files
Spring Boot will automatically find and load application.properties and application.yaml files from the following locations when your application starts:
From the classpath
The classpath root
The classpath /config package
From the current directory
The current directory
The /config subdirectory in the current directory
Immediate child directories of the /config subdirectory
The list is ordered by precedence (with values from lower items overriding earlier ones). Documents from the loaded files are added as PropertySources to the Spring Environment.
Manual import using spring.config.import
(original answer from 2021, tested against Spring Boot 2.4)
One solution is to have 3 properties files and to import
src/main/resources/application.yml - contains the application's default props
src/test/resources/application.yml - sets the profile to 'test', and imports properties from 'main'
src/test/resources/application-test.yml - contains test-specific profiles, which will override 'main'
Here is the content of src/test/resources/application.yml:
# for testing, set default profile to 'test'
spring.profiles.active: "test"
# and import the 'main' properties
spring.config.import: file:src/main/resources/application.yml
For example, if src/main/resources/application.yml has the content
ip-address: "10.7.0.1"
username: admin
and src/test/resources/application-test.yml has
ip-address: "999.999.999.999"
run-integration-test: true
Then (assuming there are no other profiles)...
when running tests,
profiles=test
--
ip-address=999.999.999.999
username=admin
run-integration-test=true
and when running the application normally
profiles=none
--
ip-address=10.7.0.1
username=admin
run-integration-test <undefined>
Note: if src/main/resources/application.yml contains spring.profiles.active: "dev", then this won't be overwritten by src/test/resources/application-test.yml
A delarative way to do that (In fact, a minor tweek to #Compito's original answer):
Set spring.profiles.active=test in test/resources/application-default.properties.
Add test/resources/application-test.properties for tests and override only the properties you need.
You can put your test specific properties into src/test/resources/config/application.properties.
The properties defined in this file will override those defined in src/main/resources/application.properties during testing.
For more information on why this works have a look at Spring Boots docs.
If you use maven, you can add this in pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<argLine>-Dspring.profiles.active=test ${argLine}</argLine>
</configuration>
</plugin>
...
Then, maven should run your integration tests (*IT.java) using this arugument, and also IntelliJ will start with this profile activated - so you can then specify all properties inside
application-test.yml
and you should not need "-default" properties.
UPDATE
added ${argLine} for this to work together with other plugins (e.g. jacoco)
To activate "test" profile write in your build.gradle:
test.doFirst {
systemProperty 'spring.profiles.active', 'test'
activeProfiles = 'test'
}
In my case I have different application.properties depending on the environment, something like:
application.properties (base file)
application-dev.properties
application-qa.properties
application-prod.properties
and application.properties contains a property spring.profiles.active to pick the proper file.
For my integration tests, I created a new application-test.properties file inside test/resources and with the #TestPropertySource({ "/application-test.properties" }) annotation this is the file who is in charge of picking the application.properties I want depending on my needs for those tests
Another programatically way to do that:
import static org.springframework.core.env.AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME;
#BeforeClass
public static void setupTest() {
System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "test");
}
It works great.
If you simply want to set/use default profile at the time of making build through maven then, pass the argument
-Dspring.profiles.active=test
Just like
mvn clean install -Dspring.profiles.active=dev
I've usually done a base class for all integration tests with common code and annotations. Do not forget make it abstract in order not to instatiate. E.g:
#SpringBootTest
#Transactional
#AutoConfigureMockMvc
#ActiveProfiles("test")
public abstract class AbstractControllerTest {
#Autowired
protected MockMvc mockMvc;
protected ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception {
return mockMvc.perform(builder);
}
}
// All annotations are inherited
class AccountControllerTest extends AbstractControllerTest {
....
The best solution I have found is the last suggestion here: https://inspeerity.com/blog/setting-default-spring-profile-for-tests-with-override-option/
The author also desribes the problem very clearly and discusses the downside of every other approach I can think of.
Create a file application-default.properties in your test resources, containing a single line:
spring.profiles.active=test
This takes advantage of the fact that Spring automatically enables a "default" profile if no other profiles were explicitly set. Now, your application-test.properties file will be used by default, for all tests.
Add spring.profiles.active=tests in your application.properties file, you can add multiple properties file in your spring boot application like application-stage.properties, application-prod.properties, etc. And you can specify in your application.properties file while file to refer by adding spring.profiles.active=stage or spring.profiles.active=prod
you can also pass the profile at the time running the spring boot application by providing the command:
java -jar-Dspring.profiles.active=localbuild/libs/turtle-rnr-0.0.1-SNAPSHOT.jar
According to the profile name the properties file is picked up, in the above case passing profile local consider the application-local.properties file
Related
everyone。When I was writing junit, I found that #ActiveProfiles corresponds to my resource directory。
I don’t understand how spring boot loads resource files,And why if I don’t specify #ActiveProfiles which application file is read by default?
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
#ActiveProfiles("test")
public class NewTutorGroupIaoTest {
}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
#ActiveProfiles("test")
public class NewTutorGroupIaoTest {
}
project directory:
project
- src
- main
- java
- resource
- test
- java
- resource
If you mark classes with #Profile("test"), you can ensure they're loaded (into the ApplicationContext) by activating the test profile - either with #ActiveProfiles("test"), spring.profiles.active=test, or a number of other ways. Classes can be excluded with #Profile("!test")
More details here: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-profiles
Also, by activating a profile, you may also activate a properties file to be picked up... You may also have files in your project like:
application.properties
application-default.properties
application-test.properties
If you have application.properties present as well as application-test.properties, application.properties forms the base config, and application-test.properties will overwrite any existing properties and may also supply additional configuration values.
If you supply/specify no profile, the default profile is activated. This will result in application.properties + application-default.properties being combined (as before).
You will see in the logs which profile is activated very near the beginning of the Spring log output.
I have a project, let's call it BaseProject, which contains a properties file defining different configurations. One of those properties could be security.password.minlength=4.
Now I have a ProjectA which builds up on BaseProject and there for depends on it.
ProjectA
|
|--BaseProject
Now in ProjectA I would like to have a default security.password.minlength of 8.
If I simply add a application.properties file to my ProjectA, containing security.password.minlength=8 and the specific property is set to 8. Al tough all the other properties form my BaseProject are ignored now, giving me the exception: java.lang.IllegalArgumentException: Could not resolve placeholder
I still would like to use all properties defined in BaseProject but solely set the security.password.minlength to a different value. How can I achieve this?
Update
Currently I let Spring do the handling of the application.properties file. Inside my application, I simply get the values from the Spring environment.
If you are using the default property handling of Spring Boot, you can make use of profiles. E.g. you could use a profile named projecta for your derived project and specify your properties in a file named application-projecta.properties. This will automatically be picked up when you specify the profile to be active (see here: http://www.baeldung.com/spring-profiles)
I found the following solution for me:
BaseProject has a application.properties which contains all the default values.
ProjectA has a projectA.properties which is I integrate with
#PropertySource("projectA.properties")
public class ProjectA {
public static void main(String[] args) {
SpringApplication.run(ProjectA .class, args);
}
}
This allows me to override any property from the BaseProject project in the projectA.properties file. The properties which I don't define in projectA.properties are still taken from the application.properties of the BaseProject`.
If I create a commons library having an application.properties defining common configurations. Like:
spring.main.banner-mode=off
How can I inherit these properties into another project where I include those commons library?
Maven:
<project ...>
<groupId>de.mydomain</groupId>
<artifactId>my-core</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<!-- this one holds the common application.properties -->
<groupId>my.domain</groupId>
<artifactId>my-commons</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
How can I inherit the configuration from my-commons to my-core?
Solution is to include the shared properties using a different name, here application-shared.properties
In shared library:
#SpringBootApplication
#PropertySource(ResourceUtils.CLASSPATH_URL_PREFIX + "application-shared.properties") //can be overridden by application.properties
public class SharedAutoConfiguration {
}
In main app:
#SpringBootApplication
#Import(SharedAutoConfiguration.class)
public class MainAppConfiguration extends SpringBootServletInitializer {
}
This way the commons/shared config gets loaded, but is though able to be overridden in application.properties of main app.
It does not work with the spring.main.banner-mode property (don't know why), but with all other properties it worked well.
I had a same motivation - to extract common configuration for typical service into a separate starter/library. I've decided to move with EnvironmentPostProcessor
My commons-web library has its own application-web.properties with, lets say, single property:
spring.main.banner-mode: off
And EnvironmentPostProcessor to pick it up
public class DefaultPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor {
#SneakyThrows
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
environment.getPropertySources()
.addLast(new ResourcePropertySource("application-web.properties"));
}
}
To make it works you need to specify a post processor (on a library side) in resources/META-INF/spring.factories this way:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.surprise.starter.web.properties.DefaultPropertiesEnvironmentPostProcessor
With such solution it's possible to pick up this config on service side and override it in regular application.properties
I'm not using #PropertySource cause there is nuance with properties ordering & detection:
...
#PropertySource annotations on your #Configuration classes. Please
note that such property sources are not added to the Environment until
the application context is being refreshed. This is too late to
configure certain properties such as logging.* and spring.main.* which
are read before refresh begins.
You can try this with Spring.
You can define your src/main/resources/application.properties in a common module as you mentionned. It will be present in classpath for other project that depends on it.
Then with Annotation #PropertySource, in other projects that depends on common module :
#Configuration
#PropertySource("classpath*:META-INF/spring/properties/*.properties")
public class Config {
...
}
Or with XML configuration :
<context:property-placeholder location="classpath*:META-INF/spring/properties/*.properties"/>
It should import all the configuration files in the give classpath directory.
Also, you should be aware that you can't have two same files in classpath. You will have a conflict.
For instance, this situation will generate a conflict :
Project A (depends on Project B) :
src/main/resources/application.properties
Project B :
src/main/resources/application.properties
You will have to rename application.properties file or to put it in a different directory.
In our Spring web applications, we use the Spring bean profiles to differentiate three scenarios: development, integration, and production. We use them to connect to different databases or set other constants.
Using Spring bean profiles works very well for the changing the web app environment.
The problem we have is when our integration test code needs change for the environment. In these cases, the integration test loads the application context of the web app. This way we don't have to redefine database connections, constants, etc. (applying the DRY principle).
We setup our integration tests like the following.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = ["classpath:applicationContext.xml"])
public class MyTestIT
{
#Autowired
#Qualifier("myRemoteURL") // a value from the web-app's applicationContext.xml
private String remoteURL;
...
}
I can make it run locally using #ActiveProfiles, but this is hard-coded and causes our tests to fail on the build server.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = ["classpath:applicationContext.xml"])
#ActiveProfiles("development")
public class MyTestIT
{ ... }
I also tried using the #WebAppConfiguration hoping that it might somehow import the spring.profiles.active property from Maven, but that does not work.
One other note, we also need to configure our code so that developers can run the web app and then run the tests using IntelliJ's test runner (or another IDE). This is much easier for debugging integration tests.
As other people have already pointed out, you can opt to use Maven to set the spring.profiles.active system property, making sure not to use #ActiveProfiles, but that's not convenient for tests run within the IDE.
For a programmatic means to set the active profiles, you have a few options.
Spring 3.1: write a custom ContextLoader that prepares the context by setting active profiles in the context's Environment.
Spring 3.2: a custom ContextLoader remains an option, but a better choice is to implement an ApplicationContextInitializer and configure it via the initializers attribute of #ContextConfiguration. Your custom initializer can configure the Environment by programmatically setting the active profiles.
Spring 4.0: the aforementioned options still exist; however, as of Spring Framework 4.0 there is a new dedicated ActiveProfilesResolver API exactly for this purpose: to programmatically determine the set of active profiles to use in a test. An ActiveProfilesResolver can be registered via the resolver attribute of #ActiveProfiles.
Regards,
Sam (author of the Spring TestContext Framework)
I had a similar problem: I wanted to run all of my integration tests with a default profile, but allow a user to override with a profile that represented a different environment or even db flavor without having to change the #ActiveProfiles value. This is doable if you are using Spring 4.1+ with a custom ActiveProfilesResolver.
This example resolver looks for a System Property, spring.profiles.active, and if it does not exist it will delegate to the default resolver which simply uses the #ActiveProfiles annotation.
public class SystemPropertyActiveProfileResolver implements ActiveProfilesResolver {
private final DefaultActiveProfilesResolver defaultActiveProfilesResolver = new DefaultActiveProfilesResolver();
#Override
public String[] resolve(Class<?> testClass) {
if(System.getProperties().containsKey("spring.profiles.active")) {
final String profiles = System.getProperty("spring.profiles.active");
return profiles.split("\\s*,\\s*");
} else {
return defaultActiveProfilesResolver.resolve(testClass);
}
}
}
And in your test classes, you would use it like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles( profiles={"h2","xyz"},
resolver=SystemPropertyActiveProfileResolver.class)
public class MyTest { }
You can of course use other methods besides checking for the existence of a System Property to set the active profiles. Hope this helps somebody.
If you want to avoid hard-coding the profile you may want to use the system property spring.profiles.active and set it to whatever you need in that particular environment e.g. we have "dev", "stage" and "prod" profiles for our different environments; also we have a "test", "test-local" and "test-server" profiles for our testing.
Remember that you can have more than one profile in that system property by using a list of comma-separated values e.g. "test,test-qa".
You can specify system properties in a maven project in the maven surefire plugin or passing them like this:
mvn -DargLine="-DpropertyName=propertyValue"
As #ElderMael mentioned you could use the argLine property of maven surefire plugin. Often when I need to run all the test with different specific Spring profiles I define additional maven profile. Example:
<profiles>
<profile>
<id>foo</id>
<dependencies>
<!-- additional dependencies if needed, i.e. database drivers ->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dspring.profiles.active=foo</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
With that approach you could easily run all the test with activated profile by maven command:
mvn clean test -Pfoo
The #ActiveProfile annotation is good but sometimes we need to run all the test with activated specific profiles and with hard-coded #ActiveProfile parameters it is a problem.
For example: by default integration test with H2 in-memory db, but sometimes you want to run test on the "real" database. You could define that additional maven profile and define Jenkins job. With SpringBoot you could also put additional properties to test/resources with name application-foo.yml (or properties) and those properties will be taken into account to.
there are many faces to this problem.
in my case, a simple addition to build.gradle already helped:
test { systemProperties = System.properties }
I would like to test that a spring
#Configuration class
can handle missing files on the classpath. E.g. when using PropertySourcesPlaceholderConfigurer. But this is just a specific example, the question is really about how to test classes that interact with the classpath (e.g. read a file located in src/main/resources in a maven project).
So in essence I would like to create a spring context where I control the classpath in the test set up code.
The test needs to be a JUnit test.
Hope below may help you
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath*:/testApplicationContext.xml"})
public class YourTestClass{
you have to create a spring context for your test and you can include the production one into it. you can replace classpath*: with a absolute location.
Regards, Rajib.
This work if it's a maven project:
move the classpath file that you want to test the absence from to a separate pom jar module, and include it wherever needed.
move the classpath test to a separate pom jar module named missing-classpath-file-test, but don't include the module with the file that you want to simulate as missing. I will be missing from the classpath only for that test.
When running missing-classpath-file-test, the file will not be on the classpath, and the error you need to reproduce is achieved.
Concerning the question on the comment bellow, with the class loaders that come with application servers and the one used on a junit test it's not possible to programmatically change the classpath.