I'm VP of Engineering at Tasktop Technologies, improving the effectiveness of developers worldwide via Tasktop's revolutionary task-focused interface, built on the Eclipse Mylyn open source framework. David has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

Patterns For Better Unit Testing With JPA

07.16.2010
| 20390 views |
  • submit to reddit

Over the years I’ve run across many projects, which tend to fall into one of two categories: those projects that have great confidence, energy and momentum, running hundreds (or thousands) of unit tests dozens of times every day; and those projects where the effort of writing and running unit tests is high enough that tests aren’t maintained and as a result no one on the team really knows if their software works. So how do we get our project into the first category? Below I highlight a few patterns and practices that significantly lower the bar to creating a solid test suite, enabling your team to adopt a practice and culture of complete test coverage.

Setup and Speed

Configuration, setup and maintenance of environments creates a significant barrier to a well-exercised and maintained test suite. Ideally we want to enable running tests with zero setup. Data-intensive applications need a database, which are notoriously hard to configure and setup. We’ll use a database for our tests, but instead of connecting to an external one the database will start up and run as part of the unit test. This gives us the following:

  • zero setup: no installation, no shared machine, no maintenance, no JDBC urls, no passwords
  • in-memory and fast
  • can run anywhere, even when not on a network
  • clean, with no rogue data that could affect our tests
  • no deadlocks

My first reaction when I saw this approach in use was “how can tests be meaningful if they're not run on the same database software that is used in production”? The answer is two-sided. We should be running these tests on the same database software. In fact, I encourage you to do that — on your continuous integration build server. The tests are meaningful in that they can make assertions and exercise your code. Database platform-specific issues can be flushed out on the build server, which can run these same tests using a different configuration.

To set this up I recommend using Apache Derby or Hypersonic. Here’s how it’s done with Hypersonic and Eclipselink as our JPA provider:

  1. add hsqldb.jar to the test project classpath (this is for Hypersonic)
  2. configure your JDBC connection to use an in-memory database URL as follows: jdbc:hsqldb:mem:tests
  3. configure your persistence.xml with the right SQL dialect. For Eclipselink this is done by setting eclipselink.target-database to HSQL as follows:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" 
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="blogDomain" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        ... snip ...
        <properties>
            <property name="eclipselink.target-database" value="HSQL"/>
            <property name="eclipselink.ddl-generation" value="create-tables"/>
            <property name="eclipselink.ddl-generation.output-mode" 
                             value="database"/>
            <property name="eclipselink.weaving" value="false"/>
            <property name="eclipselink.logging.level" value="INFO"/>
        </properties>
    </persistence-unit>
</persistence>

With this configuration the test suite will create a new in-memory database every time it’s run, and Eclipselink will create the necessary tables to persist our data. We’ve managed to eliminate the need for any kind of setup or external database to run our tests. That’s great, but now we’ve got an empty database, where does our data come from?

Test Data

Data-intensive applications need data for their tests — that’s a given. For our tests to run reliably and check specific scenarios every time, we need the data used by each test case to be the same every time tests are run. The best way to do this is to have our tests mock up the data. Mocking data can be cumbersome, however that can be alleviated by using mock factories for our domain objects. Mock factories populate domain objects with values such that they’re ready to use as-is. Where needed, tests can alter the state of domain objects to create the desired scenario. Here’s an example:

 @Test
 public void testUpdateBlog() {
  Blog blog = MockFactory.on(Blog.class).create(entityManager);
  entityManager.flush();
  entityManager.clear();
  
  blog.setName(blog.getName()+"2");
  
  Blog updatedBlog = service.updateBlog(blog);
  assertNotNull(updatedBlog);
  assertNotSame(updatedBlog,blog);
  assertEquals(blog.getName(),updatedBlog.getName());
  assertEquals(blog.getId(),updatedBlog.getId());
 }
 

In this example the test verifies that the Blog entity is properly updated by the service. Notice that the test didn’t have to populate the blog object before persisting it: all of the required values were filled in by the MockFactory.

For this to work, we’ll need a MockFactory implementation. This is what ours looks like:

/**
 * A factory for domain objects that mocks their data.
 * Example usage:
 * <pre><code>
 * Blog blog = MockFactory.on(Blog.class).create(entityManager);
 * </code></pre>
 * @author David Green
 */
public abstract class MockFactory<T> {

 private static Map<Class<?>,MockFactory<?>> factories = 
      new HashMap<Class<?>, MockFactory<?>>();
 static {
  register(new MockBlogFactory());
  register(new MockArticleFactory());
 }
 private static void register(MockFactory<?> mockFactory) {
  factories.put(mockFactory.domainClass,mockFactory);
 }
 @SuppressWarnings("unchecked")
 public static <T> MockFactory<T> on(Class<T> domainClass) {
  MockFactory<?> factory = factories.get(domainClass);
  if (factory == null) {
   throw new IllegalStateException(
    "Did you forget to register a mock factory for "+
      domainClass.getClass().getName()+"?");
  }
  return (MockFactory<T>) factory;
 }
 
 private final Class<T> domainClass;

 private int seed;
 
 protected MockFactory(Class<T> domainClass) {
  if (domainClass.getAnnotation(Entity.class) == null) {
   throw new IllegalArgumentException();
  }
  this.domainClass = domainClass;
 }

 /**
  * Create several objects
  * @param entityManager the entity manager, or null if the mocked objects
  *            should not be persisted
  * @param count the number of objects to create
  * @return the created objects
  */
 public List<T> create(EntityManager entityManager,int count) {
  List<T> mocks = new ArrayList<T>(count);
  for (int x = 0;x<count;++x) {
   T t = create(entityManager);
   mocks.add(t);
  }
  return mocks;
 }

 /**
  * Create a single object
  * @param entityManager the entity manager, or null if the mocked object
  *        should not be persisted
  * @return the mocked object
  */
 public T create(EntityManager entityManager) {
  T mock;
  try {
   mock = domainClass.newInstance();
  } catch (Exception e) {
   // must have a default constructor
   throw new IllegalStateException();
  }
  populate(++seed,mock);
  if (entityManager != null) {
   entityManager.persist(mock);
  }
  return mock;
 }

 /**
  * Populate the given domain object with data
  * @param seed a seed that may be used to create data
  * @param mock the domain object to populate
  */
 protected abstract void populate(int seed, T mock);
 

 private static class MockBlogFactory extends MockFactory<Blog> {
  public MockBlogFactory() {
   super(Blog.class);
  }
  
  @Override
  protected void populate(int seed, Blog mock) {
   mock.setName("Blog "+seed);
  }
 }

 private static class MockArticleFactory extends MockFactory<Article> {
  
  public MockArticleFactory() {
   super(Article.class);
  }
  
  @Override
  protected void populate(int seed, Article mock) {
   mock.setAuthor("First Last");
   mock.setTitle("Article "+seed);
   mock.setContent("article "+seed+" content");
  }
 }
}

This implementation uses static methods and a static initializer. It’s not very Spring-like. If you prefer you could get rid of all the statics and instead have Spring inject (autowire) your mock factories into your test classes.

In our implementation we use an integer seed to help us produce values that vary. This implementation is fairly trivial, however if needed we could apply more advanced techniques such as using data dictionaries to source data. Such mock factories should populate enough values that the mocked domain object can be persisted: all not-null properties should have values. To ensure that this is true over the lifespan of our project, it’s important to have a test:

/**
 * test that {@link MockFactory mock factories} are working as expected
 * 
 * @author David Green
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( { "/applicationContext-test.xml" })
@Transactional
public class MockFactoryTest {

 @PersistenceContext
 private EntityManager entityManager;

 @Test
 public void testCreateBlog() {
  Blog blog = MockFactory.on(Blog.class).create(entityManager);
  assertNotNull(blog);
  assertNotNull(blog.getName());
  entityAssertions(blog);
  entityManager.flush();
 }
 @Test
 public void testCreateArticle() {
  Blog blog = MockFactory.on(Blog.class).create(entityManager);
  Article article = MockFactory.on(Article.class).create(null);
  article.setBlog(blog);
  entityManager.persist(article);
  assertNotNull(article);
  assertNotNull(article.getTitle());
  assertNotNull(article.getContent());
  entityAssertions(article);
  entityManager.flush();
 }
 private void entityAssertions(AbstractEntity entity) {
  assertNotNull(entity.getId());
  assertNotNull(entity.getCreated());
  assertNotNull(entity.getModified());
 }
}

The key behind this approach is that each test mocks its own data. Where data models are signifcantly more complex, utility functions can use these mock factories to create common scenarios.

Now that we’ve got an easy way to produce data in our tests, how do we eliminate potential for side-effects between tests? That part is easy: we ensure that transactions are rolled back after every test. In our example we’re using Spring to run our tests via SpringJUnit4ClassRunner. Its default behaviour is to roll back after each test is run, however if you’re not using Spring you should have something like this in an abstract test case class:

 /**
  * Overriding methods should call super.
  */ 
 @After
 public void after() {
  if (entityManager.getTransaction().isActive()) {
   entityManager.getTransaction().rollback();
  }
 }

Summary

We’ve seen how we can make unit testing easy with a few simple patterns:

  • in-memory database
  • mocked data
  • roll back transactions after each test

By employing these simple techniques, tests are easy to write and run. Your team will get addicted to thorough test coverage as the number of tests being run increases and provides tangible feedback of their progress.

In my next article I’ll post complete working source code and show how these techniques can be taken a step further in testing Spring REST web services.

From http://greensopinion.blogspot.com/2010/07/patterns-for-better-unit-testing-with.html

Published at DZone with permission of its author, David Green.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Thomas Mueller replied on Fri, 2010/07/16 - 2:39am

You should also consider the H2 Database as alternative to HSQLDB and Apache Derby. It provides compatibility modes for Oracle, MS SQL Server, MySQL, PostgreSQL, IBM DB2, Derby, and HSQLDB.

Barry Fitzgerald replied on Fri, 2010/07/16 - 3:23am

Some interesting thoughts.

One hazard of this approach is the situation where the annotations are incorrectly mapped to the database columns. Consider the situation where I have a user object. I've incorrectly mapped the first name field to the last name database column and the last name field to the first name database column.

The tests you show would never detect this class of errors.

The only way true test for JPA mappings is to have an independent way of inserting the entities into the database - dbunit fulfils this need nicely. 

 

Josh Marotti replied on Fri, 2010/07/16 - 9:35am

I know it is a nitpick, but if your code is hitting a DB, external or internal, it is an integration test, not a unit test.

Thomas Mueller replied on Fri, 2010/07/16 - 9:56am

What about if we just call it "automated test" instead of "integration test" and "unit test". If needed, you can group then into "quick automated test" (to be run locally, before committing) and "longer running automtated test" (to be run on the build machine).

I read there is a tool that finds out what tests need to be run given a set of changes (based on code coverage), but I have no experience about that so far. Sounds cool.

Mladen Girazovski replied on Fri, 2010/07/16 - 11:11am

I agree with Barry, DBUnit would be a better approach imho, combined with this Pattern: http://xunitpatterns.com/Back%20Door%20Manipulation.html

I also agree with Josh, we should distinguish (isolated) unit tests (using Mocks etc.) from (modul/system) integrationtests, as the latter requires more setup and usually has lots more depedencies, which not only makes them slower, but also more fragile than unit tests.

 

Andrea Mattioli replied on Sat, 2010/07/17 - 2:23pm

I used a similar approach to test Hibernate based repositories in JavATE. I used classic Hibernate APIs instead of JPA but the concept is the same

Guillaume Bilodeau replied on Mon, 2010/07/19 - 10:47am

We've been using this strategy for about 2 years now and have come to the following conclusions:

  1. Great isolation between developers, something that is harder to achieve with a shared dedicated database.
  2. Quick to setup.
  3. Quick to launch, quick feedback. This is especially useful when testing your JPA mapping annotations.
  4. Usually useless when testing native queries because of differences between HsqlDb and the target database, Oracle in our case.

The last item has proven to be somewhat of a deal breaker. We still use these tests to verify our JPA mappings and test basic methods, but have stopped using it for testing native queries. This is a shame since native queries are at higher risk of containing bugs, in our case at least.

I haven't used H2 with Oracle compatibility mode - how compatible does it become? Does it support Oracle-specific SQL features, such as date arithmetic and CASE statements?

Thomas Mueller replied on Tue, 2010/07/20 - 2:28am

> H2 with Oracle compatibility mode - how compatible does it become?

It's hard to say. H2 does support some of the old style Oracle outer joins with (+). CASE statements are supported (but I believe they are not Oracle specific - other databases support them as well). Date arithmetic: H2 support substracting dates, if that's what you need. H2 doesn't support the "null is the same as an empty string" logic, but concatenating NULL with another value results in the other value in the Oracle mode. Also, the Oracle specific behavior for NULL in unique indexes.

Evan Summers replied on Tue, 2010/07/20 - 5:21am

we also take this approach :) but use H2 database with "org.h2.Driver" and url "jdbc:h2:mem" (with EclipseLink)

Thomas Kern replied on Thu, 2012/09/06 - 10:55am

I like the abstract base class approach for instantiation of the mock objects and the inevitable utility methods that seem to always be necessary. I suppose you could even subclass it further for different superclasses of mock objects you wanted to create.

I guess using private inner classes is appropriate for this example but a real project would need one separate class for each entity you wanted to mock.

http://www.java-tips.org 

Cédric Chantepie replied on Thu, 2013/12/05 - 3:42am

Old but still interesting.

I would like to add Acolyte which is a tool for testing JDBC based application.

Application using JDBC for DB connection only care about JDBC types and objects. Acolyte is a JDBC driver that can be plugged in place of production on for tests, to be able to mock up/inject JDBC results according application statement. Doing so, it's easy to test that application does what expected according the different kinds of result.

In this way, Acolyte complies with following points, previously mentioned in the article:

  • zero setup: no installation, no shared machine, no maintenance, no JDBC urls, no passwords
  • in-memory and fast
  • can run anywhere, even when not on a network
  • clean, with no rogue data that could affect our tests
  • no deadlocks

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.