DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Databases
  4. Patterns For Better Unit Testing With JPA

Patterns For Better Unit Testing With JPA

David Green user avatar by
David Green
·
Jul. 16, 10 · Interview
Like (0)
Save
Tweet
Share
45.80K Views

Join the DZone community and get the full member experience.

Join For Free

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

unit test Database Data (computing) In-memory database

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • The Path From APIs to Containers
  • Fargate vs. Lambda: The Battle of the Future
  • The 5 Books You Absolutely Must Read as an Engineering Manager
  • REST vs. Messaging for Microservices

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: