SpringApplicationBuilder is not loaded during integration tests with JUnit SpringRunner - java

It's my first question ever on stackoverflow.com.
Lately I have been looking for a way to change how Spring Boot (2.x) names its beans, created via #Component (etc) annotation.
I have found the SpringApplicationBuilder class, which thankfully allows to inject custom bean name generator:
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.beanNameGenerator(customBeanNameGenerator)
.run(args);
}
}
But the problem is, despite the fact that this solution works for production, this does not hovewer work in JUnit 4.0 unit tests, where JUnit Runner SpringRunner recreates Spring Context for testing purposes - in that case SpringApplicationBuilder is not used whatsoever, and as a result our application startup fails, becouse of autowiring multi-beans candidates problems.
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {Application.class} )
public class SomeControllerTest {
...
}
Is there any elegant way to make that unit tests use SpringApplicationBuilder as well?
Regards.

the property "classes" of #SpringBootApplication only use annotations of the classes. therefore the code for building SpringApplication is not work when run the tests.
if you want to fix this problem, you can try other ways such as specify the nameGenerator in #ComponentScan
#ComponentScan(nameGenerator = CustomBeanNameGenerator.class)
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.run(args);
}
}
ensure you class CustomBeanNameGenerator has an empty constructor.
ps: english is not my native language; please excuse typing errors.

Related

Moving to the Spring Boot. Migration of logic from the old main class

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.

Spring issue with #PostConstruct and #PreDestroy

I have a Spring application that I am trying to test with EmbededRedis. So I created a component like below to Initialize and kill redis after test.
#Component
public class EmbededRedis {
#Value("${spring.redis.port}")
private int redisPort;
private RedisServer redisServer;
#PostConstruct
public void startRedis() throws IOException {
redisServer = new RedisServer(redisPort);
redisServer.start();
}
#PreDestroy
public void stopRedis() {
redisServer.stop();
}
}
But now I am facing a weird issue. Because spring caches the context, PreDestroy doesnt get called everytime after my test is executed, but for some reason, #PostConstruct gets called, and EmbededRedis tries to start the running redis server again and again, which is creatimg issues in the execution.
Is there a way to handle this situation by any mean?
Update
This is how I am primarily defining my tests.
#SpringBootTest(classes = {SpringApplication.class})
#ActiveProfiles("test")
public class RedisApplicationTest {
Ditch the class and write an #Configuration class which exposed RedisServer as a bean.
#Configuration
public void EmbeddedRedisConfiguration {
#Bean(initMethod="start", destroyMethod="stop")
public RedisServer embeddedRedisServer(#Value("${spring.redis.port}") int port) {
return new RedisServer(port);
}
}
So I debuged the ContextInitialization as suggested by #M. Deinum.
For me, the porblem was, Our application was mocking different classes in order to mix mocking with Spring context.
Now, when you use mocks, MockitoContextInitializer also becomes part of your cache key, which results in cache miss. Reason is, The classes under mock are obviously different for different test classes.
Looking at the situation, I preferred to go ahead with #DirtiesContext to invalidate the contest after the test is done, so that I can reinitialize the context later on for different test.
Note #DirtiesContext is in a way recommended to be avoided as it slows down your tests.

How to stop utility "main" from interfering with real "main" SpringBoot application?

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.

Should I test the main() method of Spring Boot Application and how?

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();
}
}

Best way to exclude unit test #Configurations in Spring?

In my Spring Boot project, I have a main class annotated with #SpringBootConfiguration. I also have some unit tests that use #SpringApplicationConfiguration that points to an inner class that defines a Spring context for usage in my unit test (using some mocks).
I now want to write an integration test that starts the full context of my application. However, this does not work as it also picks up the Spring contexts that are defined as inner classes in other unit tests.
What would be the best way to avoid that? I did see the exclude and excludeName properties on #SpringBootConfiguration, but I am unsure how to use them.
UPDATE:
Some code to explain the problem more:
My main class:
package com.company.myapp;
#SpringBootApplication
#EnableJpaRepositories
#EnableTransactionManagement
#EntityScan(basePackageClasses = {MyApplication.class, Jsr310JpaConverters.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I have a unit test for Spring REST Docs:
package com.company.myapp.controller
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration
#WebAppConfiguration
public class SomeControllerDocumentation {
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
// Some test methods here
// Inner class that defines the context for the unit test
public static class TestConfiguration {
#Bean
public SomeController someController() { return new SomeController(); }
#Bean
public SomeService someService() { return new SomeServiceImpl(); }
#Bean
public SomeRepository someRepository() { return mock(SomeRepository.class);
}
So the unit test uses the context defined in the inner class. Now I want a different test that tests if the "normal" application context of the app starts up:
package com.company.myapp;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(MyApplication.class)
#WebAppConfiguration
public class MyApplicationTest {
#Autowired
private ApplicationContext applicationContext;
#Test
public void whenApplicationStarts_thenContextIsInitialized() {
assertThat(applicationContext).isNotNull();
}
}
This test will now not only wire the stuff it should, but also the beans from the SomeControllerDocumentation.TestConfiguration inner class. This I want to avoid.
You could use profiles: annotate the relevant configuration beans with #Profile("unit-test") and #Profile("integration-test") and inside the tests specify which profile should be active via #ActiveProfiles.
However, it sounds like you could avoid the problem altogether just by reorganising your configuration structure. It's hard to assume anything without seeing any code, though.
Since Spring Boot 1.4, the problem can be avoided by annotation the configuration in the unit tests with #TestConfiguration
I think you talk about #Ignore

Categories

Resources