Reputation: 19090
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
@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.
Let me know if I need to provide additional information.
Upvotes: 46
Views: 72515
Reputation: 186
I was having same issue and solved following this
http://www.javafixing.com/2021/10/fixed-how-to-cleanup-h2-db-after-each.html?m=1
basically you can clean the DB after every test method with:
@Sql(scripts = "clean_file.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
on clean_file.sql
you can add all the SQL statements to reset the db
Upvotes: 1
Reputation: 1085
As of JUnit 5 you could also create a custom extension and access the data source from the Spring context, like so (using 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()
}
}
}
You can then register the extension as follows:
@SpringBootTest
@ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
Now after each test the callback is executed and you can easily annotate any test classes this applies to.
Here is also a video on settings this up.
Upvotes: 2
Reputation: 3753
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.
Upvotes: 34
Reputation: 8570
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
.
Upvotes: 2
Reputation: 21
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}"
})
Upvotes: 2
Reputation: 1804
@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() {}
Upvotes: 20
Reputation: 77
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{
}
Upvotes: 2
Reputation:
Make a @Before
method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract
.
Upvotes: 1