Reputation: 1555
I have a service in spring boot with a @PostConstruct
method. In some integration tests, this method shall not be executed (the service is not used at all in these integratoin tests). So I thought I could just let the bean be created with new
inside a configuration class loaded inside this test (@ContextConfiguration
).
However, this does not work as I thought. @PostConstruct
is called anyway, even twice, as you can see below (yes, this is the whole code):
Application
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Service
@Service
public class MyService {
@PostConstruct
public void startup() {
System.out.println("@PostConstruct - " + this);
}
}
Test
@RunWith(SpringRunner.class)
@SpringBootTest(
webEnvironment = WebEnvironment.RANDOM_PORT,
classes = {DemoApplication.class})
@ContextConfiguration(classes = TestConfiguration.class)
public class DemoApplicationTests {
@Test
public void test() {
System.out.println("Test");
}
@Configuration
static class TestConfiguration {
@Bean
public MyService xxx() {
MyService myService = new MyService();
System.out.println("@Bean - " + myService);
return myService;
}
}
}
If the test is executed, the following output is printed:
:: Spring Boot :: (v2.1.1.RELEASE)
...
2018-11-30 20:34:28.422 INFO 16916 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-11-30 20:34:28.422 INFO 16916 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1573 ms
@PostConstruct - com.example.demo.MyService@41c89d2f
@Bean - com.example.demo.MyService@2516fc68
@PostConstruct - com.example.demo.MyService@2516fc68
2018-11-30 20:34:28.838 INFO 16916 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2018-11-30 20:34:29.086 INFO 16916 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 62040 (http) with context path ''
2018-11-30 20:34:29.090 INFO 16916 --- [ main] com.example.demo.DemoApplicationTests : Started DemoApplicationTests in 2.536 seconds (JVM running for 4.187)
Test
2018-11-30 20:34:29.235 INFO 16916 --- [ Thread-3] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Can anybody explain me this?
@PostConstruct
called@PostConstruct
called for a bean constructed with new
. Why is this bean managed by spring?Edit:
I tried to return a bean created with Mockito.mock(...)
rather than creating it with new
. This helps in sense that the second @PostConstruct
is not executed. So the question remains: why the first one? And how to get rid of it too?
Upvotes: 1
Views: 2099
Reputation: 5893
I'm going to explain the behavior that you're seeing in all cases, just so you know what's going on.
The first PostConstruct is called because you're running your tests with SpringRunner
and @SpringBootTest
, this is scanning your classpath and registering MyService
as a Bean because it's annotated with @Service
.
The second PostConstruct was being called because even though you were newing up MyService, you were doing so in a method annotated with @Bean
, which registered the bean in the Spring context and thus it participated in the lifecycle just the way that any other bean does (including have its @PostConstruct
and @PreDestroy
methods invoked).
If you don't want a real instance of MyService
in your SpringBootTests, you can use @MockBean
. In your SpringBootTests you'll probably want MockBean over Mock as it will mock the bean in your Spring context.
Upvotes: 2