38

我正在使用 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

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

如果我需要提供其他信息,请告诉我。

4

7 回答 7

30

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

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

于 2013-01-16T00:38:41.623 回答
15

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

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

所以我决定创建一个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
}
}

要使用 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 {
}

最后但同样重要的是,我现在可以创建测试,并且可以确定数据库处于干净模式。

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

编辑:我改进了我的解决方案。我有一个问题,有时一种测试方法破坏了我的数据库,用于测试类中所有即将进行的测试。所以我创建了注释

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

并将其添加到 CleanupDatabaseTestExectionListener。

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

在这两个片段的帮助下,我现在可以创建这样的测试:

@Test
@ClearContext
public void testWhichDirtiesDatabase() {}
于 2017-07-27T13:22:28.730 回答
3

您可以@Transactional在 Junit 类级别从org.springframework.transaction.annotation.Transactional.

例如:

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

@Transactional
public class ArmyTest{

}
于 2016-10-02T02:30:21.673 回答
1

制作一种@Before方法,从数据库中删除所有数据。您正在使用 Hibernate,因此您可以使用 HQL: delete from Contract

于 2013-01-16T09:21:46.303 回答
1

我为每个测试使用随机内存数据库解决了同样的问题:

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@TestPropertySource(properties = {
    "spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})
于 2019-11-21T15:46:47.990 回答
1

从 JUnit 5 开始,您还可以创建自定义扩展并从 Spring 上下文访问数据源,如下所示(使用 Kotlin):

class DatabaseCleanerExtension : AfterEachCallback {
  override fun afterEach(context: ExtensionContext) {
    val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)

    ds.connection.use { connection ->
      connection.prepareStatement("DELETE FROM my_table").execute()
    }
  }
}

然后,您可以按如下方式注册扩展:

@SpringBootTest
@ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }

现在在每次测试后执行回调,您可以轻松地注释任何适用的测试类。

这里还有一个关于设置的视频。

于 2021-08-03T09:47:03.673 回答
0

如果您使用 flyway 进行迁移,我使用以下模式:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
    @Autowired
    Flyway flyway;

    @BeforeAll
    public void cleanUp(){
        flyway.clean();
        flyway.migrate();
    }
}

@TestInstance允许您制作@BeforeAll非静态的,因此每个测试类只能迁移一次。如果您想为每个测试重置它,请删除类注释并更改@BeforeAll@BeforeEach.

于 2020-11-25T20:18:55.703 回答