How do I reset my database state after each unit test without making the whole test a transaction?

前端 未结 6 1828
心在旅途
心在旅途 2020-12-16 09:50

I\'m using Spring 3.1.1.RELEASE, Hibernate 4.1.0.Final, JPA 2, JUnit 4.8.1, and HSQL 2.2.7. I want to run some JUnit tests on my service methods, and after each test, I wou

相关标签:
6条回答
  • 2020-12-16 10:18

    If you use flyway for migrations, I use the following pattern:

    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class JUnit5Class {
        @Autowired
        Flyway flyway;
    
        @BeforeAll
        public void cleanUp(){
            flyway.clean();
            flyway.migrate();
        }
    }
    

    @TestInstance allows you to make @BeforeAll non static and thus you can migrate only once per test class. If you want to reset it for each test remove the class anotation and make change @BeforeAll to @BeforeEach.

    0 讨论(0)
  • 2020-12-16 10:19

    You can use @Transactional annotation at Junit class level from org.springframework.transaction.annotation.Transactional.

    For example:

    package org.test
    import org.springframework.transaction.annotation.Transactional;
    
    @Transactional
    public class ArmyTest{
    
    }
    
    0 讨论(0)
  • 2020-12-16 10:24

    I solve the same problem using a random memory database for each test:

    @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
    @TestPropertySource(properties = {
        "spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
    })
    
    0 讨论(0)
  • 2020-12-16 10:27

    @DirtiesContext was no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.

    @Before was also not a good solution for me as I have to create @Before in each integration test.

    So I decided to create an TestExecutionListener which recreates the database after each test. (With Liquibase, but it also works with Flyway and normal SQL)

    public class CleanupDatabaseTestExecutionListener
    extends AbstractTestExecutionListener {
    
    public final int getOrder() {
        return 2001;
    }
    
    private boolean alreadyCleared = false;
    
    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {
        if (!alreadyCleared) {
            cleanupDatabase(testContext);
            alreadyCleared = true;
        } else {
            alreadyCleared = true;
        }
    }
    
    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        cleanupDatabase(testContext);
    }
    
    private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
        ApplicationContext app = testContext.getApplicationContext();
        SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
        springLiquibase.setDropFirst(true);
        springLiquibase.afterPropertiesSet(); //The database get recreated here
    }
    }
    

    To use the TestExecutionListenere I created a custom test annotation

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = OurderApp.class)
    @TestExecutionListeners(mergeMode = 
    TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        listeners = {CleanupDatabaseTestExecutionListener.class}
    )
    public @interface OurderTest {
    }
    

    Last but not least, I can now create tests and I can be sure that the database is in a clean mode.

    @RunWith(SpringRunner.class)
    @OurderTest
    public class ProductSaveServiceIntTest {
     }
    

    EDIT: I improved my solution a bit. I had the problem that sometime one test method destroyed my database for all upcoming tests within the test class. So I created the annotation

    package com.ourder.e2e.utils;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ClearContext {
    }
    

    and added this to the CleanupDatabaseTestExectionListener.

    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {
        if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
            cleanupDatabase(testContext);
        }
        super.afterTestMethod(testContext);
    }
    

    with help of these two snippets I am now able to create tests like this:

    @Test
    @ClearContext
    public void testWhichDirtiesDatabase() {}
    
    0 讨论(0)
  • 2020-12-16 10:27

    Make a @Before method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.

    0 讨论(0)
  • 2020-12-16 10:32

    Since you are using hibernate, you could use the property hibernate.hbm2ddl.auto to create the database on startup every time. You would also need to force the spring context to be reloaded after each test. You can do this with the @DirtiesContext annotation.

    This might add a bit extra overhead to your tests, so the other solution is to just manually delete the data from each table.

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