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)
Related
I am very new to Spring Boot and development, so, I am stuck with a problem. I have an old project that needs to be migrated to Spring Boot. The original main method has super cool multi-threaded logic. In my understanding public static void main(String[] args) was entry point to the program, now after creating Spring Boot project #springbootapplication is entry point. How to access the original logic of a method? Should it somehow be transform? I spent hours looking for a suitable solution but no luck. Could you point me? Any help is appreciated :)
You have to use #SpringBootApplication, but also need to modify the main method something like:
#SpringBootApplication
public class YourMainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(YourMainApplicationClass.class, args);
}
}
This will start your application.
Then move your original code of your main method to a new class, which has annotation #Component. Implement CommandLineRunner, and override the run method. So something like:
#Component
public class YourOldMainClass implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//Your code here
}
}
When your application starts, spring will load 'near everything' with annotation into its container, so your #Component annotated class should be also loaded. The CommandLineRunner with overrode run method will auto call your method at startup.
Also, don't forget to include necessary spring boot jars next to your project, or to your build automation tool - like Maven - if you use it.
I have a SpringBoot app that has been working fine:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class },
scanBasePackageClasses = { IndexSyncController.class, IndexerService.class })
#ImportResource("classpath:/spring.xml")
public class AppLauncher {
public static void main(String[] args) {
SpringApplication.run(AppLauncher.class, args);
}
And also currently sitting in src/test/java, I have a "utility" which has its own main method. This too has been working fine, but can only launched in my development environment:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
WebMvcAutoConfiguration.class })
public class AuditApp {
public static void main(String[] args) {
SpringApplication.run(AppLauncher.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
...
};
}
Now I have a new requirement
I need to take the AuditApp utility from src/test/java and make it available in the production jar.
So I just move it from src/test/java to src/main/java right? Nope! Very first obstacle I ran into when I do that, is that the main application tries to run the commandLineRunner(). Because of past experience with a similar requirement, I believe this is just the tip of the iceberg and there will be additional issues.
Is there a canonical, general best-practice or checklist for how to accomplish this?
The first thing to consider is the real reason of your problem.
You missed out packages of mentioned classes, but looking at your symptoms, I suspect your AuditApp is in the same package, or in a subpackage of one the AppLauncher is in (or is in package scanned by config declared inside imported spring.xml).
The #SpringBootApplication is an abbreviation for (amongst other) #SpringBootConfiguration and #ComponentScan.
Because of this the AppLauncher treats AuditApp as a #SpringBootConfiguration on a package scan and instantiates beans created within this config. The AuditApp.main method is never called, but the beans declared in the config are instantianted.
Let's say you have AppLauncher in com.yourapp and AuditApp in com.yourapp.audit.
If you move your utility application to the package which is not under the package scan for AppLauncher, like com.yourauditapp. The CommandLineRunner bean won't be created when running the original application. And the AuditApp won't affect the original application.
The only other way it can interfere is when it introduces dependencies which may trigger some Spring-Boot autoconfigurations.
When I create Spring Boot Application it generates 2 classes:
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
And the second on is test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AppTest {
#Test
public void contextLoads() {
}
}
Like you can notice contextLoads test is empty. How should I provide correct test for contextLoad? It should stay empty maybe? Is it correct? Or I should put something inside?
UPDATE:
Why this test should stay? If I have more tests in application I'll know if spring context is loaded or nope. Isn't it just excessive.
I readed answers like this but it didn't gave me a clear answer.
Actually, the main() method of the Spring Boot Application is not covered by the unit/integration tests as the Spring Boot tests don't invoke the main() method to start the Spring Boot application.
Now I consider that all answers of this post seem overkill.
They want to add a test of the main() method to make a metric tool happy (Sonar).
Loading a Spring context and loading the application takes time.
Don't add it in each developer build just to win about 0.1% of coverage in your application.
I added an answer about that.
Beyond your simple example and the other post that you refer to, in absolute terms it may make sense to create a test for the main() method if you included some logic in. It is the case for example as you pass specific arguments to the application and that you handle them in the main() meethod.
Leave it empty. If an exception occurs while loading the application context the test will fail. This test is just saying "Will my application load without throwing an exception"
Update for Second Question
The reason you want to keep this test is that when something fails you want to find the root cause. If you application context is unable to load, you can assume all other tests that are failing are due to this. If the above test passes, you can assume that the failing tests are unrelated to loading the context.
When you build a Spring boot application using Spring Initializer. It auto creates Application and its Test Class
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#SpringBootTest
class ApplicationTest {
#Test
void contextLoads() {
}
}
Note the use of #SpringBootTest annotation on test class which tells Spring Boot to look for a main configuration class (one with #SpringBootApplication, for instance) and use that to start a Spring application context. Empty contextLoads() is a test to verify if the application is able to load Spring context successfully or not.
You do not need to provide any test cases for empty method as such. Though you can do something like this to verify your controller or service bean context:-
#SpringBootTest
public class ApplicationContextTest {
#Autowired
private MyController myController;
#Autowired
private MyService myService;
#Test
public void contextLoads() throws Exception {
assertThat(myController).isNotNull();
assertThat(myService).isNotNull();
}
}
I am running a spring boot application that uses TestNG as the testing framework. My tests are set up like this:
A parent class, which is in charge of setup logic and takes care of all of the configuration stuffs:
#ContextConfiguration(classes = {TestingConfig.class}, initializers = ConfigFileApplicationContextInitializer.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(locations = "classpath:application.yml")
public abstract ParentTestClass extends AbstractTestNGSpringContextTests {
#Autowired
private ServiceClient serviceClient;
#BeforeSuite
public void beforeClass() {
Assert.assertNotNull(serviceClient);
serviceClient.doSomeSetupWork();
}
}
There are multiple child test classes. Each on inherits form the parent test class so that they share the same setup logic.
public ChildTestClass1 extends ParentTestClass {
#Test
public void someTest() {
...
}
// More tests not shown
}
public ChildTestClass2 extends ParentTestClass {
#Test
public void anotherTest() {
...
}
// More tests not shown
}
The serviceClient is a client for one of the web services that the test suite depends on. I am making calls with the service client to set up the data in the other service before running the test cases.
The problem is this: previously I was using the #BeforeClass annotation, which meant that the parent class's setup method was being run once for every child test class. This was ok, but it was really slow waiting for the same setup to be run multiple times.
So I thought to myself: I'll just change the #BeforeClass annotations in the ParentTestClass to be #BeforeSuite instead! That will solve all of my problems!
Wrong.
Now when I run it, the Assert.assertNotNull(serviceClient); line in the beforeClass() method of the parent class fails. In short, Spring dependencies aren't being injected into the #BeforeSuite annotated method, even tho they were being injected in the method when it was annotated with #BeforeClass.
Any thoughts here? I'd really appreciate it!
I believe this is working as designed. From looking at how the implementation is built within org.springframework.test.context.testng.AbstractTestNGSpringContextTests (from which you extend), the dependencies are injected into your test class via the org.springframework.test.context.support.DependencyInjectionTestExecutionListener#injectDependencies (this is a listener).
All the listeners including the DependencyInjectionTestExecutionListener is invoked only via org.springframework.test.context.testng.AbstractTestNGSpringContextTests#springTestContextPrepareTestInstance which is a #BeforeClass(alwaysRun=true) classified method.
So your dependencies aren't available to you until and unless this #BeforeClass annotated method runs to completion. So you would have to move out your #BeforeSuite method and have it work with a #BeforeClass annotation only.
If you don't need your service setup to be done multiple times, then you would need to add an edit check in your test code that does the setup ONLY IF ITS NOT DONE.
Here is the solution to all your problem.
#Override
#BeforeSuite
protected void springTestContextPrepareTestInstance() throws Exception {
super.springTestContextPrepareTestInstance();
}
Hope this helps you to inject the dependencies.
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