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.
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.
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 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.
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.
Tags: fitnesse, JPA, JPA implementation patterns, Spring
Filed under Java, JPA, JPA Implementation Patterns, Testing | 6 Comments »
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.
[...] Testing [...]
“(Ever tried to refactor 50+ insert statements?).”
Yes. No big deal.
You can write this thing called a “program” to regenerate your insert statements.
Which best practices exist for testing JPA classes without a container, more specifically how can the “@PersistenceContext” work correctly without a container ?
@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.
[...] 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 [...]