Search code examples
javaspringjunitspring-test

How can Spring's test annotation @Sql behave like @BeforeClass?


How can I tell the @Sql annotation to run only once for the class, and not for each @Test method?

Like having the same behaviour as @BeforeClass?

@org.springframework.test.context.jdbc.Sql(
     scripts = "classpath:schema-test.sql",
     executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
public class TestClass {
      @Test
      public void test1() {
        //runs the @Sql script
      }

      @Test
      public void test2() {
        //runs the @Sql script again
      }
}

Solution

  • For JUnit 5, the straight forward clean solution:

    @MyInMemoryDbConfig
    //@Sql(value = {"/appconfig.sql", "/album.sql"}) -> code below is equivalent but at class level
    class SomeServiceTest {
        @BeforeAll
        void setup(@Autowired DataSource dataSource) {
            try (Connection conn = dataSource.getConnection()) {
                // you'll have to make sure conn.autoCommit = true (default for e.g. H2)
                // e.g. url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1;MODE=MySQL
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("appconfig.sql"));
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("album.sql"));
            }
        }
        // your @Test methods follow ...
    

    but when your database connections are not configured with autoCommit = true you'll have to wrap all in a transaction:

    @RootInMemoryDbConfig
    @Slf4j
    class SomeServiceTest {
        @BeforeAll
        void setup(@Autowired DataSource dataSource,
                @Autowired PlatformTransactionManager transactionManager) {
            new TransactionTemplate(transactionManager).execute((ts) -> {
                try (Connection conn = dataSource.getConnection()) {
                    ScriptUtils.executeSqlScript(conn, new ClassPathResource("appconfig.sql"));
                    ScriptUtils.executeSqlScript(conn, new ClassPathResource("album.sql"));
                    // should work without manually commit but didn't for me (because of using AUTOCOMMIT=OFF)
                    // I use url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1;MODE=MySQL;AUTOCOMMIT=OFF
                    // same will happen with DataSourceInitializer & DatabasePopulator (at least with this setup)
                    conn.commit();
                } catch (SQLException e) {
                    SomeServiceTest.log.error(e.getMessage(), e);
                }
                return null;
            });
        }
        // your @Test methods follow ...
    

    Why clean solution?

    Because according to Script Configuration with @SqlConfig:

    The configuration options provided by @Sql and @SqlConfig are equivalent to those supported by ScriptUtils and ResourceDatabasePopulator but are a superset of those provided by the <jdbc:initialize-database/> XML namespace element.

    Bonus

    You can mix this approach with other @Sql declarations.