badera
badera

Reputation: 1555

@PostConstruct and bean created with new in configuration class

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?

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

Answers (1)

lane.maxwell
lane.maxwell

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

Related Questions