JPA implementation patterns: Testing

In the previous blog in the JPA implementation patterns series, I talked about the three default ways of mapping inheritance hierarchies using JPA. And introduced one non-standard but quite useful method. This week I will discuss various approaches to testing JPA code.

What to test?

The first question to ask is: what code do we want to test? Two kinds of objects are involved when we talk about JPA: domain objects and data access objects (DAO's). In theory your domain objects are not tied to JPA (they're POJO's, right?), so you can test their functionality without a JPA provider. Nothing interesting to discuss about that here. But in practice your domain objects will at least be annotated with JPA annotations and might also include some code to manage bidirectional associations (lazily), primary keys, or serialized objects. Now things are becoming more interesting...

(Even though such JPA specific code violates the POJO-ness of the domain objects, it needs to be there to make the domain objects always function the same way. Whether inside or outside of a JPA container. The managing of bidirectional associations and using UUIDs as primary keys are nice examples of this. In any case, this is code you most certainly need to test.)

Of course, we'd also need to test the DAO's, right? An interesting question pops up here: why do we want to test the DAO's? Most of them just delegate to the JPA provider and testing the JPA provider makes no sense unless we are writing "learning tests" (see also Robert Martin's Clean Code) or developing our own JPA provider. But the combination of the DAO's and the JPA specific part of the domain objects is testworthy.

What to test against?

Now that we know what to test, we can decide what to test against. Since we are testing database code, we want our test fixture to include a database. That database can be an embedded in-memory database such as HSQLDB (in memory-only mode) or a "real" database such as MySQL or Oracle. Using an embedded database has the big advantage of being easy to set up; there is no need for everyone running the tests to have a running MySQL or Oracle instance. But if your production code runs against another database, you might not catch all database issues this way. So an integration test against a real database is also needed, but more on that later.

For most tests we need more than just the database. We need to set it up correctly before a test and after the test we need leave it in a usable state for the next test to run. Setting up the schema and filling the database with the right data before running the test are not that hard to do (a.k.a. left as an exercise for the reader 😉 ), but returning the database to a usable state after the test is a more difficult problem. I've found a number of approaches to this problem:

  • The Spring Framework includes the a test framework that uses transactions to manage the state of your test fixture. If you annotate your test to be @Transactional, the SpringJUnit4ClassRunner will start a transaction before each test starts and roll back that transaction at the end of the test to return to a known state. If you are still using JUnit 3.8 you can extend the AbstractTransactionalSpringContextTests base class for the same effect. This might seem nice but in practice I've found this method to be unsatisfactory for a number of reasons:
    1. By default the JPA context is not flushed until the transaction is committed or a query is executed. So unless your test includes a query, any modifications are not actually propagated to the database which can hide problems with invalid mappings and such. You could try and explicitly invoke EntityManager.flush before the end of the test, but then the tests don't represent real scenario's anymore.
    2. Also, saving an entity and then retrieving it in the same session does not uncover those nasty lazy loading issues. You're probably not even hitting the database as the JPA provider will return a reference to the object that you just saved!
    3. Finally, in a test you might like to first store some data in the database, then run the tests, and finally check that the right data was written out to the database. To test this properly you need three separate transactions without the first two transactions being rolled back.
  • If you use an embedded in-memory database that database will be clean when you run the first test and you won't need to worry about leaving it in a good state after all the tests are run. This means you will not have to roll back any transactions and can have multiple transactions within one test. But you might have to do something special between each test. For example, when using the Spring TestContext framework you can use the @DirtiesContext annotation to reinitialize the in-memory database between tests.
  • If you cannot use an in-memory database or re-initializing it after every test is too expensive, you can try and clear all the tables after every test (or before every test). For example, DbUnit can be used to delete all data from your test tables or truncate all test tables. Foreign key contraints may get in the way though, so you will want to temporarily disable referential integrity before performing these operations.

At what scope to test?

The next thing is to decide on the scope of the tests. Will you write small unit tests, larger component tests, or full-scale integration tests? Because of the way JPA works (the leakiness of its abstraction if you will), some problems might only surface in a larger context. While testing the persist method on DAO in isolation is useful if you want to know whether the basics are correct, you will need to test in a larger scope to shake out those lazy loading or transaction handling bugs. To really test your system you will need to combine small unit tests with larger component tests where you wire your service facades with the DAOs under test. You can use an in-memory databases for both tests. And to complete your test coverage you will need an integration test with the database to be used in production using a tool such a Fitnesse. Because we are not specifically testing the JPA code in that case, having unit tests on a smaller scale will help you pinpoint DAO bugs more quickly.

What to assert?

One final thing to tackle is what to assert in the tests. It might be that your domain objects are mapped to an existing schema in which case you want to make sure that the mapping is correct. In this case you would like to use raw JDBC access to the underlying database to assert that the right modifications were made to the right tables. But if the schema is automatically generated from the JPA mapping you probably will not care about the actual schema. You'll want to assert that persisted objects can be correctly retrieved in a new session. Direct access to the underlying schema with JDBC is not necessary and would only make such test code brittle.

I'm pretty sure that I have not covered all test scenarios in this blog because testing database code is a very complicated area. I would love to hear how you test your database code, whether it uses JPA or some other persistency mechanism.

Comments (6)

  1. Lars Vonk - Reply

    July 12, 2009 at 1:26 pm

    Hi Vincent,

    Luckily I have just finished a blog with some TDD tips :-). I think one of the main aspects is that in most applications it does not really matter what storage technology you use. As long as you define your tests in a high level language you can easily change from in-memory to Oracle for instance. The good thing is that whenever you change you have tests that proof your code is still working.

    Another tip specifically for inserting test data in the database: Don't use SQL insert scripts and stay away from tools like DBUnit where you need to define your testdata in XML. Why? Because this really gets in your way when needing to refactor the database. (Ever tried to refactor 50+ insert statements?).
    What I recommend is to setup your test data in java objects using Builder and ObjectMothers, then use your dao's to insert those into the database.

  2. [...] Testing [...]

  3. Gene De Lisa - Reply

    August 20, 2009 at 1:24 pm

    "(Ever tried to refactor 50+ insert statements?)."

    Yes. No big deal.
    You can write this thing called a "program" to regenerate your insert statements.

  4. Stefan Lecho - Reply

    March 19, 2010 at 8:53 am

    Which best practices exist for testing JPA classes without a container, more specifically how can the "@PersistenceContext" work correctly without a container ?

  5. Vincent Partington - Reply

    March 29, 2010 at 8:57 pm

    @Stefan Lecho: I have good experiences doing unit testing with a JPA provider that is configured by Spring to connect to an in-memory HSQLDB database.

  6. [...] layer is isn’t unittested, it’s integration tested (as explained by yet another Vincent here). As a rule of thumb, we always try to keep the configuration as identical to the deployment [...]

Add a Comment