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

前端 未结 6 1831
心在旅途
心在旅途 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: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() {}
    

提交回复
热议问题