How to re-create database before each test in Spring?

后端 未结 9 2187
盖世英雄少女心
盖世英雄少女心 2020-12-07 16:30

My Spring-Boot-Mvc-Web application has the following database configuration in application.properties file:

spring.datasource.url=jdbc:h2:tcp://         


        
相关标签:
9条回答
  • 2020-12-07 17:11

    You could also try out https://www.testcontainers.org/ which helps you to run databases inside containers and you can create a fresh database for each test run too. It will be very slow though, since each time a container has to be created and the database server has to be started, configured and then migrations have to be run, then the test can be executed.

    0 讨论(0)
  • 2020-12-07 17:15

    If you are looking for an alternative for the @DirtiesContext, this code below will help you. I used some code from this answer.

    First, setup the H2 database on the application.yml file on your test resources folder:

    spring: 
      datasource:
        platform: h2
        url: jdbc:h2:mem:test
        driver-class-name: org.h2.Driver
        username: sa
        password:
    

    After that, create a class called ResetDatabaseTestExecutionListener:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.TestContext;
    import org.springframework.test.context.support.AbstractTestExecutionListener;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.HashSet;
    import java.util.Set;
    
    public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
    
        @Autowired
        private DataSource dataSource;
    
        public final int getOrder() {
            return 2001;
        }
    
        private boolean alreadyCleared = false;
    
        @Override
        public void beforeTestClass(TestContext testContext) {
            testContext.getApplicationContext()
                    .getAutowireCapableBeanFactory()
                    .autowireBean(this);
        }
    
        @Override
        public void prepareTestInstance(TestContext testContext) throws Exception {
    
            if (!alreadyCleared) {
                cleanupDatabase();
                alreadyCleared = true;
            }
        }
    
        @Override
        public void afterTestClass(TestContext testContext) throws Exception {
            cleanupDatabase();
        }
    
        private void cleanupDatabase() throws SQLException {
            Connection c = dataSource.getConnection();
            Statement s = c.createStatement();
    
            // Disable FK
            s.execute("SET REFERENTIAL_INTEGRITY FALSE");
    
            // Find all tables and truncate them
            Set<String> tables = new HashSet<>();
            ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES  where TABLE_SCHEMA='PUBLIC'");
            while (rs.next()) {
                tables.add(rs.getString(1));
            }
            rs.close();
            for (String table : tables) {
                s.executeUpdate("TRUNCATE TABLE " + table);
            }
    
            // Idem for sequences
            Set<String> sequences = new HashSet<>();
            rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
            while (rs.next()) {
                sequences.add(rs.getString(1));
            }
            rs.close();
            for (String seq : sequences) {
                s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
            }
    
            // Enable FK
            s.execute("SET REFERENTIAL_INTEGRITY TRUE");
            s.close();
            c.close();
        }
    }
    

    The code above will reset the database (truncate tables, reset sequences, etc) and is prepared to work with H2 database. If you are using another memory database (like HsqlDB) you need to make the necessary changes on the SQLs queries to accomplish the same thing.

    After that, go to your test class and add the @TestExecutionListeners annotation, like:

    @TestExecutionListeners(mergeMode =
            TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
            listeners = {ResetDatabaseTestExecutionListener.class}
    )
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class CreateOrderIT {
    

    This should work.

    If you not see any performance difference between this approach and @DirtiesContext, probably you are using @MockBean inside of your tests, what mark the context as dirty and automatically reload the Spring context.

    0 讨论(0)
  • 2020-12-07 17:16

    Actually, I think you want this:

    @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
    

    http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html

    @DirtiesContext may be used as a class-level and method-level annotation within the same class. In such scenarios, the ApplicationContext will be marked as dirty after any such annotated method as well as after the entire class. If the DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context will be marked dirty after each test method in the class.

    0 讨论(0)
提交回复
热议问题