Search code examples
spring-bootspring-boot-test

How to execute code in a SpringBootTest before the Application is run?


I have a SpringBoot based command line application. The application creates or deletes some records in a database. It does so not directly via JDBC but rather through a special API (instance variable dbService).

The application class looks like this:

@SpringBootApplication
public class Application implements CommandLineRunner {

  @Autowired
  private DbService dbService;
  
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
  
  @Override
  public void run(String... args) {
    // Do something via dbService based on the spring properties
  }

}

Now I'd like to create a SpringBoot test that would run the whole application with a configuration specially prepared for the test.

I run the test with an in-memory DB (H2) which is empty at the test start. Hence I'd like to insert some records into the DB -- as the setup for the test. The code for inserting the records must be executed

  1. After the Spring context has been loaded -- so that I can use the bean dbService.

  2. Before the Application is run -- so that the application runs with the prepared DB.

Somehow I fail to implement the above two points.

What I have so far is this:

@SpringBootTest
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@ActiveProfiles("specialtest")
public class MyAppTest {
 
  @Autowired
  private DbService dbService;
  
  private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
 
  // The expectation is that this method is executed after the spring context
  // has been loaded and all beans created, but before the Application class
  // is executed.
  @EventListener(ApplicationStartedEvent.class)
  public void preparedDbForTheTest() {
    // Create some records via dbService
    logger.info("Created records for the test");
  }
 

  // This test is executed after the application has run. Here we check
  // whether the DB contains the expected records.
  @Test
  public void testApplication() {
    // Check the DB contents
  }
 
}

My problem is that the the method preparedDbForTheTest does not seem to get executed at all.

According to the SpringBoot docs, the event ApplicationReadyEvent is sent exactly when I want to execute the setup code. But somehow the code is not executed.

If I annotate the method with @Before... (I tried several variants of it) then it gets executed, but after the Application class has run.

What am I doing wrong?


Solution

  • Test classes aren't Spring-managed beans so things like @EventListener methods will be ignored.

    The most conventional solution to your problem would be to add some @TestConfiguration that declares the @EventListener:

    @SpringBootTest
    @DirtiesContext(classMode = ClassMode.AFTER_CLASS)
    public class MyAppTest {
      
      private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
     
      @Test
      public void testApplication() {
      }
      
      @TestConfiguration
      static class DatabasePreparation {
        @EventListener(ApplicationStartedEvent.class)
        public void preparedDbForTheTest() {
          logger.info("Created records for the test");
        }
      }
     
    }
    

    A @TestConfiguration is additive so it'll be used alongside your application's main configuration. The preparedDbForTheTest method will now be called as part of refreshing the application context for the tests.

    Note that, due to application context caching, this method won't be called for every test. It will only be called as part of refreshing the context which may then be shared among several tests.