How do I reset my database state after each unit test without making the whole test a transaction?
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.
@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() {}
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{
}