10

I have a class I'm unit testing that requires fairly extensive database setup before the individual test methods can run. This setup takes a long time: for reasons hopefully not relevant to the question at hand, I need to populate the DB programatically instead of from an SQL dump.

The issue I have is with the tear-down. How can I easily rollback all the changes made in the db setup phase?

I'm currently using Hibernate + Spring Transactional Testing support, such that my individual test methods are wrapped in transactions.

One solution would be to do the db setup within each test method, such that the db setup would be rolled back automatically. However, the test methods would take forever to run since each method would need to re-prep the database.

Any other ideas? Basically, I'm looking for a way to run my db setup, run my individual tests (each wrapped in a transaction which gets rolled-back after execution), and then roll-back the initial db setup. Any ideas on making this working in a Hibernate / Spring / Junit fashion? Is there a Hibernate "drop all tables" equivalent command?

Brian Ferris
  • 7,557
  • 5
  • 25
  • 27

8 Answers8

6

Are you stuck with a specific database vendor? If not, you could use an in-memory database, such as HSQLDB. When you are done with the tests you just throw away the state. This is only appropriate if the tables can be empty at the start of the test suite (before your programmatic setup, that is).

You still need to create tables, but if everything is neatly mapped using Hibernate you can use the hbm2ddl to generate your tables. All you have to do is add the following to your test session factory definition:

<session-factory>
    ...
    <property name="hibernate.hbm2ddl.auto">create</property>
    ...
</session-factory>

If this solution seems applicable I can elaborate on it.

waxwing
  • 18,547
  • 8
  • 66
  • 82
  • Sometimes I forget about the simplest options. Bonus points to you. – Brian Ferris May 05 '09 at 07:59
  • So by accepting this as the solution does that mean you opted for the db setup/tear down per individual test? – Adam B Jun 12 '09 at 19:46
  • I actually went with setting up the db once for a set of tests defined in the same class using a static @BeforeClass Junit method. But it would be easy enough to do on per-test basis with a @Before JUnit method. – Brian Ferris Apr 29 '10 at 06:32
  • @Brian - Can you share how you set up a database from within a static method with no access to an application context or getClass()? – Dave Jun 30 '10 at 22:03
  • This work only for the simplest of the simplest scenarios. As soon as data is involved this approach doesn't ... in contrary is contraproductive – Christoph Henrici Jan 26 '22 at 15:20
0

Hibernate has a neat little feature that is heavily under-documented and unknown. You can execute an SQL script during the SessionFactory creation right after the database schema generation to import data in a fresh database. You just need to add a file named import.sql in your classpath root and set either create or create-drop as your hibernate.hbm2ddl.auto property.

http://in.relation.to/Bloggers/RotterdamJBugAndHibernatesImportsql

Oded Peer
  • 2,377
  • 18
  • 25
0

You may want to look at @AfterClass annotation, for Junit 4. This annotation will run when the tests are done.

http://cwiki.apache.org/DIRxDEV/junit4-primer.html

James Black
  • 41,583
  • 10
  • 86
  • 166
0

DNUnit should help you in this regard. You can create separate data sets for each individual test case if you wish.

0

DBUnit will help a lot with this. You could theoretically turn off autocommits on JDBC, but it will get hairy. The most obvious solution is to use DBUnit to set your data to a known state before you run the tests. IF for some reason you need your data back after the tests are run, you could look at @AfterClass on a suite that runs all of your tests, but it is generally considered a better practice to set up your tests and then run them, so that if the test fails, it is not just because it didn't have a prestine environment due to a failure to clean up an different test. You ensure that each test sets up its environment directly.

Yishai
  • 90,445
  • 31
  • 189
  • 263
  • wouldn't using DBUnit be conceptually equivalent to SQL scripting that was ruled out in the question? – topchef May 05 '09 at 03:19
  • I didn't mention it specifically in the question, but I am familiar with DBUnit and I have been avoiding it because I didn't want to have to maintain a ton of DBUnit test fixtures when it was easier to generate the DB programatically. However, I may bite the bullet lacking a better solution. – Brian Ferris May 05 '09 at 04:50
0

One solution that you may want to consider is to use a "manual" rollback or compensating transaction in db tear down. I suppose (and if it's not then it should be a trivial add-on to your Hibernate entities) all your entities have datetime create attribute indicating when they were INSERTed into the table. Your db setup method should record time before everything else. Then you have rather simple procedure for db tear down to delete all entities that were created after time recored in db setup.

Of course, this won't work for updates in db setup... But if you have limited number of updates then consider saving pristine image for this type of data and restore it during db tear down.

topchef
  • 19,091
  • 9
  • 63
  • 102
0

If you're working with relatively small database, and with a DBMS that can do backups/exports of it relatively fast (like MS SQL Server), you can consider creating a database backup before the tests, and then restore it when all testing is complete. This enables you to set-up a development/testing database and use it as a starting state for all your tests.

I did it with native JDBC, executing ''backup database'' and ''restore database'' T-SQL in-between tests, and it worked reasonably well.

However, this approach is dependent on having the DBMS server on your local machine (for reasonable speed), you having sufficient privileges (which than should not be a problem), and the total size of database not exceeding a few tens on MB - at least in my experience.

javashlook
  • 10,341
  • 1
  • 26
  • 33
0

Is there a reason that you have to have a connection to the database to run your unit tests? It sounds like it might be easier to refactor your class so that you can mock the interaction with the database. You can mock classes (with some exceptions) as well as interfaces with EasyMock (www.easymock.org).

If your class relies on a complex pre-existing state in a connected database, it would probably be easier to write faster executing tests using mocks. We don't know what the size of your project is or how often your tests are run, but execution time might be something to think about, especially in a large project.

Paul Morie
  • 15,528
  • 9
  • 52
  • 57