@Transactional
annotation for wrapping tests in a transaction started and rolled back by the test container, to keep the tests from modifying the database. This is just what you need for deterministic, repeatable database tests.On the other hand, due to this approach, the transaction boundaries in your test system differ from the ones in your production system, which can lead to errors in production which were never noticed in your tests suites.
I wouldn't go as far as saying that transactional tests should be considered harmful, but at least you should be aware of the side effects which may or may not be harmless in your specific use case.
For tests with real transactions, you need to take care of cleaning up the database yourself.
JUnit rules are a neat way of doing this. Here is an example:
Using the CleanDatabaseRule
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/META-INF/spring/test-context.xml") public class RealTransactionTest { @Rule @Inject public CleanDatabaseRule cleanDatabase; @Inject private UserDao userDao; @Test public void testFindUsers() { userDao.createUsers("wilma", secret); assertThat(userDao.findAllUsers().size(), is(1)); } }
This test class has the usual
@RunWith
and @ContextConfiguration
annotations, to enable the Spring Test Context, but the @Transactional
annotation is missingThe
CleanDatabaseRule
is marked with @Rule
. JUnit requires all rule members to be public. Given that our rule internally works with a persistence unit to be injected by Spring, the rule itself is configured as a singleton Spring bean to be injected into our test class.This rule takes care of deleting all database content before and after each test method.
CleanDatabaseRule Implementation
Our
CleanDatabaseRule
extends JUnit's ExternalResourceRule
which has before()
and after()
methods for dealing with a given external resource (the database in our case) before and after running a test method.The level of cleanup performed by this rule is up to you: you can drop the database and create a new one, you can restore a given database dump, or simply truncate all tables, which is what I'm doing here.
The following example uses MySQL syntax to disable all foreign keys before deleting the tables:
public class CleanDatabaseRule extends ExternalResource { @PersistenceUnit private EntityManagerFactory emf; @Override protected void before() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); // disable foreign keys em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 0").executeUpdate(); truncateTables(em); // reenable foreign keys em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 1").executeUpdate(); em.getTransaction().commit(); } @Override protected void after() { before(); } }
Note that we're injecting an
EntityManagerFactory
via @PersistenceUnit
instead of the usual EntityManager
This is because the entity managers injected by Spring do not permit manual transactions. Using an EntityManagerFactory
, we can create our own EntityManager
and control or own transactions. In a database schema with lots of foreign key constraints, it is useful to disable them while cleaning up, to avoid the headache of having to delete tables in the correct order.
The
truncateTables()
method is left as an exercise. :-)
3 comments:
Man your post is so great!, Just was I was searching!
This was very helpful. Thanks!
Brilliant post!
Post a Comment