如何在每次单元测试后重置数据库状态而不将整个测试作为事务处理?

时间:2022-12-10 09:44:01

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 would like any data written to the in-memory database to be rolled back. However, I do NOT want the entire test to be treated as a transaction. For example in this test

我使用的是Spring 3.1.1.RELEASE,Hibernate 4.1.0.Final,JPA 2,JUnit 4.8.1和HSQL 2.2.7。我想在我的服务方法上运行一些JUnit测试,并且在每次测试之后,我希望回滚写入内存数据库的任何数据。但是,我不希望将整个测试视为事务。例如在这个测试中

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest 
{
    …

    @Autowired
    private ContractService m_contractService;

    @Test
    public void testUpdateContract()
    {
        // Add the contract
        m_contractService.save(m_contract);
        Assert.assertNotNull(m_contract.getId());
        // Update the activation date by 6 months.
        final Calendar activationDate = Calendar.getInstance();
        activationDate.setTime(activationDate.getTime());
        activationDate.add(Calendar.MONTH, 6);
        m_contract.setActivationDate(activationDate.getTime());
        m_contractService.save(m_contract);
        final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
        Assert.assertEquals(foundContracts.get(0), m_contract);
    }   // testUpdateContract

there are three calls to the service, ("m_contractService.save", "m_contractService.save", and "m_contractService.findContractByOppId") and each is treated as a transaction, which I want. But I don't know how to reset my in-memory database to its original state after each unit test.

有三个服务调用(“m_contractService.save”,“m_contractService.save”和“m_contractService.findContractByOppId”),每个调用都被视为一个事务,我想要它。但我不知道如何在每次单元测试后将内存数据库重置为其原始状态。

Let me know if I need to provide additional information.

如果我需要提供其他信息,请与我们联系。

4 个解决方案

#1


14  

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.

由于您正在使用hibernate,因此您可以使用属性hibernate.hbm2ddl.auto每次启动时创建数据库。您还需要在每次测试后强制重新加载spring上下文。您可以使用@DirtiesContext注释执行此操作。

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

这可能会给您的测试增加额外的开销,因此另一个解决方案就是手动删除每个表中的数据。

#2


2  

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

您可以在org.springframework.transaction.annotation.Transactional的Junit类级别使用@Transactional注释。

For example:

例如:

package org.test
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class ArmyTest{

}

#3


2  

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

@DirtiesContext对我来说没有解决方案,因为整个应用程序上下文被破坏了,必须在每次测试后创建 - >耗时很长。

@Before was also not a good solution for me as i have to create @Before in each IntegrationTest

@Before对我来说也不是一个好的解决方案,因为我必须在每个IntegrationTest中创建@Before

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)

所以我决定创建一个TestExecutionListener,它在每次测试后重新创建数据库。 (使用liquibase,但它也适用于flyway和普通的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

要使用TestExecutionListenere,我创建了一个自定义测试注释

@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 {
 }

#4


0  

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

创建一个@Before方法,从中删除数据库中的所有数据。您正在使用Hibernate,因此您可以使用HQL:从Contract中删除。

#1


14  

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.

由于您正在使用hibernate,因此您可以使用属性hibernate.hbm2ddl.auto每次启动时创建数据库。您还需要在每次测试后强制重新加载spring上下文。您可以使用@DirtiesContext注释执行此操作。

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

这可能会给您的测试增加额外的开销,因此另一个解决方案就是手动删除每个表中的数据。

#2


2  

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

您可以在org.springframework.transaction.annotation.Transactional的Junit类级别使用@Transactional注释。

For example:

例如:

package org.test
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class ArmyTest{

}

#3


2  

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

@DirtiesContext对我来说没有解决方案,因为整个应用程序上下文被破坏了,必须在每次测试后创建 - >耗时很长。

@Before was also not a good solution for me as i have to create @Before in each IntegrationTest

@Before对我来说也不是一个好的解决方案,因为我必须在每个IntegrationTest中创建@Before

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)

所以我决定创建一个TestExecutionListener,它在每次测试后重新创建数据库。 (使用liquibase,但它也适用于flyway和普通的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

要使用TestExecutionListenere,我创建了一个自定义测试注释

@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 {
 }

#4


0  

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

创建一个@Before方法,从中删除数据库中的所有数据。您正在使用Hibernate,因此您可以使用HQL:从Contract中删除。