spring 如何在每次单元测试后重置我的数据库状态而不使整个测试成为事务?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14343893/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I reset my database state after each unit test without making the whole test a transaction?
提问by Dave
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.
如果我需要提供其他信息,请告诉我。
采纳答案by Solubris
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.
这可能会给您的测试增加一些额外的开销,因此另一种解决方案是手动删除每个表中的数据。
回答by Simon Ludwig
@DirtiesContextwas no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.
@DirtiesContext对我来说没有解决方案,因为整个应用程序上下文都被破坏了,必须在每次测试后创建 -> 花了很长时间。
@Beforewas also not a good solution for me as I have to create @Beforein each integration test.
@Before对我来说也不是一个好的解决方案,因为我必须@Before在每个集成测试中创建。
So I decided to create an TestExecutionListenerwhich 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 {
}
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;
包 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.
并将其添加到 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() {}
回答by Arun
You can use @Transactionalannotation at Junit class level from org.springframework.transaction.annotation.Transactional.
您可以@Transactional在 Junit 类级别从org.springframework.transaction.annotation.Transactional.
For example:
例如:
package org.test
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class ArmyTest{
}
回答by TangZero
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}"
})
回答by TangZero
Make a @Beforemethod in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.
制作一种@Before从数据库中删除所有数据的方法。您正在使用 Hibernate,因此您可以使用 HQL: delete from Contract。

