34

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.

Dave
  • 17,420
  • 96
  • 300
  • 582
  • why do you want to avoid the transaction? – AlexWien Jan 15 '13 at 18:04
  • See also http://stackoverflow.com/questions/24854527/run-all-junit-tests-indepentently-in-eclipse-reloading-spring-context-each-time – Raedwald Jul 20 '14 at 23:02
  • Possible duplicate of [How to re-create database before each test in Spring?](https://stackoverflow.com/questions/34617152/how-to-re-create-database-before-each-test-in-spring) – Dherik Jun 13 '19 at 17:17

6 Answers6

25

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.

Solubris
  • 3,333
  • 2
  • 17
  • 32
  • 8
    I added "@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)" at the class level and it did what I need. There is extra overhead but that doesn't matter in my case. Rock on – Dave Jan 16 '13 at 16:27
  • Yes, I explicitly had to set the classMode = ClassMode.AFTER_EACH_TEST_METHOD). THe default is "After Class", but I wanted a fresh DB ready for reach Unit Test method. – Stealth Rabbi Jan 27 '17 at 13:30
13

@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() {}
Community
  • 1
  • 1
Simon Ludwig
  • 1,397
  • 17
  • 24
  • 1
    which bean should I get from context if I want to use built-in libraries? I have very simple app, and I don't need Liquibase or Flyway. – klubi Sep 13 '17 at 16:16
  • Do you want to clear your database tables or recreate the whole sql scheme ? – Simon Ludwig Sep 14 '17 at 22:25
  • Thank you! I need to use **afterTestMethod** instead of **afterTestClass** to clean the database between each test execution – sebasira Jan 09 '19 at 11:58
3

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{

}
Vinay Prajapati
  • 6,025
  • 4
  • 36
  • 75
Arun
  • 55
  • 1
1

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

1

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}"
})
TangZero
  • 11
  • 1
0

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.

simibac
  • 4,392
  • 1
  • 19
  • 30