Developing lightweight Java web applications – Part 4: Testing the persistence using Spring


Now that all persistence classes are ready, we are going to see if they work the way we want. As we are going to use Spring in the web application, we can also use it for the tests as well, because the Spring IoC container is suitable for standalone applications and provides easy to configure testing facilities.

(Some people have asked me to use the more... function in these articles, so I will try that here.)

Getting the sources

As always, you can either go the project website or checkout the associated tag from the source control system:

svn checkout http://jpokerstats.googlecode.com/svn/tags/tutorial_part_04

Changes that became necessary

While writing this part of the tutorial, I had to change some of the classes to work the way I wanted. The most important change is switching the temporal type startTime and endTime in the Game class from TemporalType.TIME to TemporalType.TIMESTAMP. The reason behind this is the fact, that times saved to the database and loaded again are no longer equal (in terms of equals()) to the times before the save. The times contained are identical, but something else isn't, so I had to switch as timestamps compare correctly.

As I am also learning while writing this, it may happen from time to time that I need to revise the code from a previous part of the tutorial. I will inform you of these changes as I do now.

Configuring Spring

We need a configuration file to configure Spring persistence, so we'll create one for testing purposes. Don't be discouraged by all the declarations in there, I'm going to explain them later on.

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:tx="http://www.springframework.org/schema/tx"
  5. xmlns:p="http://www.springframework.org/schema/p"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx
  7. http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
  8.  
  9.   <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
  10.  
  11.   <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
  12.  
  13.   <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory"/>
  14.  
  15.   <tx:annotation-driven/>
  16.  
  17.   <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
  18.     <property name="jpaProperties">
  19.       <props>
  20.         <prop key="toplink.logging.level">FINE</prop>
  21.         <prop key="toplink.jdbc.driver">org.hsqldb.jdbcDriver</prop>
  22.         <prop key="toplink.jdbc.url">jdbc:hsqldb:mem:jpokerstats_test</prop>
  23.         <prop key="toplink.jdbc.user">sa</prop>
  24.         <prop key="toplink.jdbc.password"></prop>
  25.         <prop key="toplink.target-database">oracle.toplink.essentials.platform.database.HSQLPlatform</prop>
  26.         <prop key="toplink.ddl-generation">drop-and-create-tables</prop>
  27.       </props>
  28.     </property>
  29.     <property name="persistenceUnitName" value="jpokerstats"/>
  30.   </bean>
  31. </beans>

You can read about all this configuration options in the excellent Spring reference handbook. It might actually help you to understand what happens here. For starters, I recommend reading chapter 3 and chapter 12.

First, the obvious part: I have declared a bean called entityManagerFactory and this will tell Spring how to configure itself to work with our persistent classes. This is also where we configure the database settings, although these are specific to the JPA implementation we are going to use. These options are simply passed through to Toplink.

Next I have defined a bean called transactionManager. Spring does not only manage our database connections, it also manages our transactions so that we don't need to handle them ourselves or let the container handle them via JTA as not all containers provide container managed transactions. The Hint <tx:annotation-driven/> tells Spring that we will signal the use of transactions via annotations. This feature is pretty cool, you'll see more of it in a later part.

The remaining two unnamed beans enable two other nice features. PersistenceAnnotationBeanPostProcessor is responsible for resolving any @PersistenceContext or @PersistenceUnit annotations within our application and inject an EntityManager or EntityManagerFactory instance, just like an EJB 3 container would do. The other class, PersistenceExceptionTranslationPostProcessor, will wrap any JPA specific exception with a corresponding Spring exception. As you may have read already, Spring supports many ORM providers, not just JPA. By wrapping the exceptions, your application only has to handle Spring exceptions while the underlying persistence layer can be freely exchanged.

Writing the test itself

The tests will use the Spring testing extension, a neat functionality that makes testing quite easy. I created a test case named SpringJpaTest that extends AbstractJPATests. This will cause Spring to initialize itself when the test is run and it provides a testing framework for JPA of which this test isn't going to use much. To use all features provided by this superclass, we would have to configure a real persistence container with a DataSource and some load-time weaving. Sounds ugly? It is, at least for now, so we'll stick to the simple things. The @ContextConfiguration tell Spring to load the configuration for this test class from a file in the same package named just like the class but suffixed the -context.xml. That's why our configuration file is named SpringJpaTest-context.xml. To let Spring do it's thing, we tell the class to run with the special SpringJUnit4ClassRunner.

JAVA:
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration
  3. public class SpringJpaTest extends AbstractJpaTests
  4. {
  5.   @PersistenceContext
  6.   private EntityManager _manager;
  7.  
  8.   // other stuff
  9.   // ...
  10.  
  11. }

The _manager field will be injected by Spring with an EntityManager just before the test is run. Each test method runs within it's own transaction and the changed made to the database are rolled back when the method finishes. The method annotated with @Beforewill be called by JUnit once before each test method is run and we will use this mechanism to prepare the data to work with. Spring makes sure that the @Before-method runs in the same transaction as the actual test method.

I'm using HSQLDB for testing as well for the live application as it has a small footprint and is easy to configure. Toplink will create the necessary tables for us because we configured the toplink.ddl-generation option to drop-and-create-tables. This provides us with a clean database for each test cycle.

Pitfalls

The preparation of the data is pretty straightforward and contains no magic whatsoever. But there are some pitfalls I need to show you. German speaking readers might remember this article about bi-directional relationships in JPA. We face the same problem here: a game references 0 or more game results and each of this game results references the game. For this to work and foremost for this to be saved to the database, we need to make both of these assignments (only an excerpt of the code is shown):

JAVA:
  1. _game = new Game();
  2.   final GameResult result1 = new GameResult();
  3.  
  4.   // assignment result -> game
  5.   result1.setGame(_game);
  6.  
  7.   // assignment game -> result
  8.   _game.addResult(result1);

I've seen people struggle with this and finally, they came up with the great idea that when one of this assignments is made, the other assignment is triggered and made automatically. So they fitted both method (in this case setGame() and addResult()) with some clever code like this:

JAVA:
  1. public void setGame(final Game game)
  2. {
  3.   if (null != game)
  4.     game.addResult(this);
  5.   _game = game;
  6. }
  7.  
  8. public void  addResult(final GameResult result)
  9. {
  10.   if (null != result)
  11.     result.setGame(this);
  12.   _results.add(result)
  13. }

As you may easily see, this code will result in an infinite loop. I've tried to devise some collisionfree adaptation of this, but there is no clean way to do this within the entity classes themselves. In later development steps, we will use helper classes to do this work for us.

The next problem I came across was Toplink's caching mechanism. The test works as follows: it creates some data objects, saves them to the database, loads them again and checks if they have been saved correctly. Unfortunately (at least for the testing) Toplink caches the objects currently attached to the EntityManager. If they get selected again (as they get in the test), the database itself is not queried and the results are delivered from the cache. To be able to test if really something was written to the database, we need to call _manager.flush() to make Toplink actually save to the database and then _manager.clear() to detach all entities saved so far.

The last pitfall: the test method itself has to be annotated with @Transactional or it will not participate in the same transaction used for createTestData(). As transactions are not commited by default, the database would be empty again by the time the test is run.

What's next?

As this tutorial is about developing web applications, the next part finally deal with some web technology called Facelets, the templating framework for JSF. So stay tuned!

Tags: , , , , , ,

, , , , , ,

  1. Bisher keine Kommentare.
(wird nicht veröffentlicht)