3

I know how to configure Spring/JUnit to rollback after each test case. What I am after is a way to start and roll back one transaction for all test cases.

I am using @BeforeClass to prepare my HSQL DB for several test cases. Then I want to rollback the changes after the end of all test cases in @AfterClass.

What is the best way to achieve this rollback?

Here is my code example:

@BeforeClass
public static void setupDB(){
ApplicationContext context = new ClassPathXmlApplicationContext(
        "classpath:/spring/applicationContext-services-test.xml");
//- get beans and insert some records to the DB
...
}

@AfterClass
public static void cleanUp(){
   ??? what should go here?
}

Any idea on the best way to do rollback in AfterClass?

Thanks to all..

DhafirNz
  • 627
  • 3
  • 9
  • 20

4 Answers4

5

In case it's acceptable to you to roll back after each test and to use Spring, the following snippet from my project might help you:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/net/sukharevd/shopzilla/model/application-context-dao.xml" })
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class)
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class HibernateCategoryDaoTest extends AbstractTransactionalJUnit4SpringContextTests {
Dmitriy Sukharev
  • 1,084
  • 2
  • 11
  • 20
  • Thanks for your reply. I am using SpringJUnit4ClassRunner for my other unit tests classes, the same way you have here. What I want is for my all unit test cases to use the same DB setup. That is why I am doing my setup once and let every unit test work on the same setup. – DhafirNz Jul 08 '12 at 23:29
3

You probably don't want to be doing a rollback after the class, but after each test. Tests are not guaranteed to run in the same order each time, so you might get different results. Unit tests should be isolated.

You can use the spring test runner to annotate your class and rollback transactions

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration(defaultRollback=true)
public static class YourTestClass {

    @Test
    @Transactional
    public void aTest() {
        // do some db stuff that will get rolled back at the end of the test
    }

}

In general though you should also try to avoid hitting a real database in unit tests. Tests that hit the database are generally integration level tests (or even more coarse grain tests like acceptance tests). The DBUnit http://www.dbunit.org/ framework is used to stub out databases for unit tests so you dont' need to use a real database.

Jeff Storey
  • 56,312
  • 72
  • 233
  • 406
  • Thanks for your reply. Yes I agree with what you said, and I use SpringJUnit4ClassRunner for my other test cases. This is , however, a special case where I have to setup the DB with a lot of records that is applicable to all test cases. To re-run the setup before each test case (@Before) is tedious and will slow down the unit test. Am also not using real DB, it is a temporarily HSQL file bases test DB, but I don't want to setup to effect other test classes, so I would like to be able to rollback my setup when I am done with it. – DhafirNz Jul 08 '12 at 23:34
  • @DhafirNz, after reading your comment to Dmitriy about wanting to use the same database for all unit tests, why not set it up once before you run any tests? Is that what you're trying to do? – Jeff Storey Jul 09 '12 at 00:03
  • 1
    Yes I could do that and we normally do that, but in this particular case the setup takes considerable time to finish, running it for every test case will be very slow. – DhafirNz Jul 09 '12 at 00:55
  • Why not do it once as part of the pre-test setup such as a maven goal or whatever build system you're using – Jeff Storey Jul 09 '12 at 01:37
  • But that wouldn't work, would it? Because the test data is not for all test classes, and there is no guarantee my test class will kick in first. Other integration tests could start first and they will wipe out the DB anyway. Besides, I insert the test data programically in Java using the DAO layer (around 2000 records) in controlled combinations so the test data i sin the same place as the test cases. – DhafirNz Jul 09 '12 at 03:07
  • Sorry I misunderstood...you are right that wouldn't work. Since the database setup takes so long I would definitely look into stubbing it out with dbunit – Jeff Storey Jul 09 '12 at 12:20
2

I managed to solve this issue using just Spring/JUnit (without using DBUnit). In short, the solution was to call transactionManager.getTransaction(def).setRollbackOnly(); in @BeforeClass.

Let me first explain what I was trying to do. My main motive was around this flow:

1. Start transaction
2. Insert load test data
3. run several test cases on the same test data
4. rollback test data.

Since I am building my load test data in @BeforeClass, I was looking to rollback in @AfterClass. This seems to be unnecessary as I can simply instruct the transaction to be rollback only in my @BeforeClass!

So here is how I did it:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/spring/applicationContext-services-test.xml")
@TestExecutionListeners(inheritListeners = false, listeners = { SpecialDependencyInjectionTestExcecutionListener.class })
@TransactionConfiguration(defaultRollback = true)
@Transactional
public class loadTest {
...
private static HibernateTransactionManager transactionManager;
...

@BeforeClass
public static void setupDB() {
  //- set the transaction to rollback only. We have to get a new transaction for that.
  DefaultTransactionDefinition def = new DefaultTransactionDefinition();
  def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
  transactionManager.getTransaction(def).setRollbackOnly();
  ...
  //- start loading the data using the injected services.
  ...
}

This helped to rollback at the end of the class.

P.S. The SpecialDependencyInjectionTestExcecutionListener is an extension to DependencyInjectionTestExecutionListener which I used to override beforeTestClass to force the application.context to to be loaded before calling @BeforeClass. Credit goes to Dmitriy in highlighting this Listener which was the hint to solve another problem which i had in my mind.

Thanks to everyone who helped in highlighting and suggestions which collectively led me to this solution.

Dhafir

DhafirNz
  • 627
  • 3
  • 9
  • 20
0

Let's pretend you weren't using Spring. (Because using Spring the other answers here are better.) Then you would have three choices:

  1. Use dbunit to take care of the data load/cleanup for you. (The site is down at the moment but if you Google it, you can see some tutorials.)
  2. Create manual deletes for the manual updates
  3. Create a rollback point in your database as the first step of setup. Here's how to do so in Oracle.
Jeanne Boyarsky
  • 12,156
  • 2
  • 49
  • 59
  • Thanks for the suggestions Jeanne, I'll look into DBUnit. I guess I was after the easiest way to achieve single rollback for the entire test class since we are using Spring 9we have to to test the configuration at the same time). – DhafirNz Jul 09 '12 at 20:33