Ref - Maven Profiles
I don't want to specify profile through command line arguments while running. Because, if I change it locally, then changes have to be made thoughout CI stream.
Scenario:
There are basically two profiles "PROD" and "TEST". I have annotated methods using these. Basically, I connect different databases under different profiles.
While the test classes are annotated with #ActiveProfile("TEST")
I run tests using mvn clean test-compile failsafe:integraton-test and run application using mvn springboot:run
Problem:
Case 1:
Issue: Only PROD related methods run for PROD and TEST.
If I don't use #Profile("PROD") and only use #Profile("TEST") and I use #ActiveProfiles("TEST") as specified in reference. Both mvn commands without any flag specified above only use the beans which are not annotated with profiles.
Case 2:
Issue: PROD doesn't run.
If I use both PROD and TEST without any flag, mvn clean test-compile failsafe:integraton-test command runs perfectly (only when #ActiveProfile("TEST") is specified otherwise I'd have to send a flag -Dspring.profiles.active=TEST) but springboot:run doesn't work as it cannot pick up any database configuration and no active profile found.
This is the case even when I Annotate PROD profiles with both PROD and #Primary
Surprisingly even If I provide command line argument the result is the same. mvn springboot:run -Dspring.profiles.active=PROD
If I annotate the main class where these profiles are run with #ActiveProfiles("PROD"), the result is the same.
Ideal Scenario/Expected:
To run tests using mvn test-compile failsafe:integration-test preferably without flag. (Already acheived but prod isn't working)
To run prod using mvn springboot:run emphasis of no flags.
Preferable changes:
Java annotations: Annotations such as #Profile, #ActiveProfile, #Primary which tells Spring-boot or maven what to use.
POM.xml: setting profiles for directing maven what to use.
in short i think you just need to add spring.profiles.active=PROD to your application.properties
I put together an example which prints the contents of a bean which differs between prod ant test, hope this helps.
When running the below code i get the following output:
mvn spring-boot:run: The String in the context is: PROD
mvn clean test: The String in the context is: TEST
ProfilesApplication.java:
#SpringBootApplication
public class ProfilesApplication implements CommandLineRunner {
Logger logger = LoggerFactory.getLogger(ProfilesApplication.class);
#Autowired
private String profiledString;
public static void main(String[] args) {
SpringApplication.run(ProfilesApplication.class, args);
}
#Bean
#Profile("PROD")
public String prodString() {
return new String("PROD");
}
#Bean
#Profile("TEST")
public String testString() {
return new String("TEST");
}
#Override
public void run(String... args) throws Exception {
logger.info("The String in the context is: {}", profiledString);
}
}
application.properties:
spring.profiles.active=PROD
ProfilesApplicationTests.java:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("TEST")
public class ProfilesApplicationTests {
#Test
public void contextLoads() {
}
}
Related
I am using Hashi Corp vault in Spring Boot project. I am able to run the project without any issue. But when I run unit tests, secret-id and role-id are not being passed. I tried the following but got an exception saying both are empty. Tried hard coding the values, that didn't work either
EmployeeTest.java
#RunWith(SpringRunner.class)
#DataJpaTest
#ActiveProfiles(value = "ide")
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class EmployeeTest
{
private final Logger logger= LoggerFactory.getLogger(getClass());
#Autowired
EmployeeRepository employeeRepository;
#Test
public void getEmployeeById()
{
Employee employee=employeeRepository.getOne(13L);
logger.info(employee.toString());
}
}
Update:
I am able to pass secret-id and role-id through VM arguments but still properties are not resolving
Okay, it turns out when using profile from src/main/resources/application-ide.yml in spring boot test, properties are not being replaced by vault vaules. Copying the same file to src/test/resources/application-ide.yml fixes the issue.
TL;DR
For Spring Boot testing always better to use properties file from src/test/resources rather than src/main/resources
I have a moderately heavy springboot service, it takes 10-15 seconds to boot on a happy flow, and (1-2) minutes to fail on a retry/failover flow. This is ok for my business flows, and is how I expect a healthy service to behave.
I have integration tests (that run some end-to-end flows in my service), that can only test the actual integration status while the test machine (or dev machine) is connected to a specific VPN.
I want to auto skip integration tests if I'm not connected to VPN.
consider the following code
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Server.class}, // auto scans a bunch of components
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // slow loading context
public class IntegrationTest {
#BeforeClass
public static void beforeClass() {
Assume.assumeTrue(DnsTool.vpnConnected()); // fast failing test
}
#Test
public void testIntegration() {
// some test logic
}
}
When the assumptions pass, my tests run, and all is good.
When the assumptions fail, my tests get skipped, but only after trying to load my expensive context.
How can I avoid the long running time for my test suite?
Things I tried:
Subclassing SpringJUnit4ClassRunner, and overriding isTestMethodIgnored.
Adding a TestExecutionListener, and throwing the assumption exception in beforeTestClass
These made no impression on Spring, and the context got loaded any way.
Things I didn't try:
Lazy init comes with 2.2.X next stable release of spring I think.
Lazy init potentially makes my problem go away, but I feel like there should be some easy spring-test/junit fix that I'm missing.
Thanks in advance for the help.
To me, this sounds like something that you shouldn't do in tests at all.
Tests (at least IMHO), are supposed to check the business cases and assume that the environment is set up and ready.
Maybe it worth to delegate this functionality to build tool and CI.
Example:
Define a profile in maven (or whatever build tool you use) that will run integration tests that require VPN. Define profile that will run all the rest of integration tests as well.
Activate the profile if some system property is available.
In CI tool (like Jenkins) as a part of CI even before you run maven, run the script that will check the VPN connection. Based on the results set the system properties and run maven with these properties. The required profiles will be loaded and all the tests / only tests that do not require VPN will be run.
Update
If you need to make it work from Spring (and it looks like you prefer this way),
Spring has a special annotation called #IfProfileValue
By default, it matches against system properties and if the value doesn't match the test gets ignored.
It looks something like this (and note that you can put this annotation on class as well, then it will work for all test methods in the class):
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTestClass {
#IfProfileValue(name = "os.name", values = {"Linux"})
#Test
public void testMe() {
// will run only in linux, otherwise
// won't even try to load an
// application context
....
}
}
This covers the case when you resolve the VPN connectivity externally and run the tests with a property. However, if you want to implement the VPN connectivity check in java, this annotation along not enough because it can work only with Java system properties, so in order to work with custom logic you need to implement org.springframework.test.annotation.ProfileValueSource:
public class VPNConnectivityProfileValueSource implements ProfileValueSource {
private String vpnEnabled = "true";
public VPNConnectivityProfileValueSource () {
// no spring context is available here
ClassPathResource resource = new ClassPathResource("vpn-config.properties");
if (resource.exists()) {
// read the VPN address,
//
//this.testProps = PropertiesLoaderUtils.loadProperties(resource);
// invoke your utility, check the connectivity, etc.
this.vpnEnabled = ...
}
}
#Override
public String get(String key) {
// this is important method,
if(key.equals("vpn.enabled") {
return this.vpnEnabled;
}
else return System.getProperty(key);
}
}
The last thing is to make the test aware of the ProfileValueSource:
For this there is another special annotation that you put on the test:
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
All in all it the test can look like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
#IfProfileValue(name = "vpn.enabled", value = "true")
public class MyTestClass {
#Test
public void testMe() {
....
}
}
All the classes/annotations I've mentioned reside in package org.springframework.test.annotation
I have a Springboot application that runs a CLI app.
Here's my main class:
#SpringBootApplication
public class MyApplication {
#Autowired
GameClient gameClient;
#PostConstruct
public void postConstruct(){
gameClient.runGame();
}
public static void main(String[] args) {
SpringApplication.run(GameApplication.class, args);
}
}
When I run the command mvn package to generate a JAR, Spring executes the postConstruct() method and starts my CLI application instead of properly generating my JAR.
When I remove the postConstruct() the JAR is successfully generated, but I need this method becaus it is responsible for running my CLI app.
How can I solve it?
The issue is that gameClient.runGame() appears to block your test, either by running infinitely, or by requesting user input. If you have any tests running your Spring boot application, your test (and your build) will block as well.
Even though you can pass the -Dmaven.test.skip=true parameter to skip tests (as mentioned in this answer), it still means that that specific test is broken. Either remove it if you don't need it, or make sure that gameClient.runGame() isn't invoked during a test by doing the following:
Move your logic to a separate class that implements the CommandLineRunner (or ApplicationRunner) interface:
#Component
public class GameRunner implements CommandLineRunner {
#Autowired
GameClient gameClient;
#Override
public void run(String...args) throws Exception {
gameClient.runGame();
}
}
After that, annotate the component with the #Profile annotation, for example:
#Component
#Profile("!test") // Add this
public class GameRunner implements CommandLineRunner {
#Autowired
GameClient gameClient;
#Override
public void run(String...args) throws Exception {
gameClient.runGame();
}
}
By using the #Profile annotation, you can tell Spring to only load the component when certain profiles are active. By using the exclamination mark !, we tell Spring to only load the component if the test profile is not active.
Now, within your test, you can add the #ActiveProfiles("test") annotation. This will enable the test profile when running the test, and that means that the GameRunner bean will not be created.
I solved it by skiping the tests when generationg the JAR:
mvn package -Dmaven.test.skip=true
mvn package was invoking my tests, which internally initialized all the beans and, as part of initialization, Spring invokes #PostConstruct methods.
First you need to update in pom file.
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.eg</groupId>
<artifactId>eg</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
then run this command (mvn package -Dmaven.test.skip=true)
I am using SpringBoot for latest development.Profile based configuration we have so there are 3 files are there
application-{env}.properties, env means dev,stage and prod
When I run the project locally in eclipse using -Dspring.profiles.active=dev run configuration it is picking configurations from application-dev.properties. But If I try to run a jUnit, it is expecting application.properties otherwise throwing error.I put a application.properties it is working fine.Please tell me how to configure JUnit with profile based so that which will pick application-dev.properties.Please give your input on this, thanks in advance.
#SpringBootTest
#RunWith(SpringRunner.class)
public class ProcessingServiceTest {
#Test
public void testlet2MeIn() throws Exception{
}
}
Did you try setProperty ?
#SpringBootTest
#RunWith(SpringRunner.class)
public class ProcessingServiceTest {
public ProcessingServiceTest(){
System.setProperty("spring.profiles.active","dev");
}
#Test
public void testlet2MeIn() throws Exception{
}
}
you can specify the properties files using #TestPropertySourceand specify which file should be used to load properties for tests
#TestPropertySource(locations = "classpath:application-dev.properties")
Or
the simplest way is to use this on your test class #ActiveProfiles("dev")
I have an integration tests class extended from AbstractTestNGSpringContextTests. I want to run test class twice, each time with a different profile programmatically and not by hard-coding profile and configuration xml as below.
#ContextConfiguration(locations = { "classpath:MyBatisBeanConfiguration.xml" })
#ActiveProfiles("cloud")
public class MyIntegrationTests extends AbstractTestNGSpringContextTests
{
#Test
public void myTest()
{
//do something
}
}
You can use the VM argument spring.profiles.active to enable certain spring profile(s) in your tests (or any other application for that matter).
Example: -Dspring.profiles.active=cloud,someOtherProfile