Sunday, February 19, 2017

How to do unit test on JPA

It will be very inconvenient to test JPA DAO classes if you need to build and deploy the application to an application server. You can actually do unit tests on JPA without using any application server. The following is one example.

1. The entity class.


@Entity
@Table(name = "T_SAMPLE")
public class SampleData implements Serializable {

 private SampleDataId id;
 private String xyz;
 
 @EmbeddedId
 public SampleDataId getId() {
  return this.id;
 }

 public void setId(final SampleDataId id) {
  this.id = id;
 }


 @Column(name = "XYZ")
 public String getXyz() {
  return this.xyz;
 }

 public void setXyz(final String xyz) {
  this.xyz = xyz;
 }

}
2. The DAO class to be tested.
packge com.example.dao;

@Repository
public class SampleDataDAO {
 protected static final Logger LOG = Logger.getLogger(SampleDataDAO.class);

  @PersistenceContext
  private EntityManager entityManager;

 /**
  * Returns List of SampleData type.
  */
 public List<SampleData> getSomeData() {
   List<SampleData> results = new ArrayList<SampleData>();
   EntityManager entityManager = getEntityManager();
   final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
   final CriteriaQuery<SampleData> query = builder
     .createQuery(SampleData.class);
   final Root<SampleData> root = query.from(SampleData.class);
   List<Predicate> predicates = new ArrayList<Predicate>();
   query.select(root);
    ......
   query.where(predicates.toArray(new Predicate[] {}));

   results = entityManager.createQuery(query).getResultList();
   return results;
 }

public void saveSomeData(SampleData sampleData) {
   EntityManager entityManager = getEntityManager();
   entityManager.persist(sampleData); 
 }
}
3. The test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/testApplicationContext.xml" })
public class SampleDataDAOTest {

 @Autowired
 private SampleDataDAO sampleDataDAO;

 @Test
 public void testRead() {
  sampleDataDAO.getSomeData(...);
 }

 @Test
 @Transactional
 public SampleData testSave() {
  SampleData sampleData = new SampleData();
  sampleData.setXyz("bla");
  sampleDataDAO.saveSomeData(sampleData);
  SampleData savedData = sampleDataDAO.getSampleDataById(sampleData.getId());
  Assert.assertNotNull(savedData);
  return savedData;
 }
}

Notes:

  1. The method testSave() has the annotation @Transactional. Without this annotation, the test won't work. The savedData will be null. The @Transactional annotation has to appear somewhere. If the method saveSomeData of sampleDataDAO is annotated with @Transactional, then it is also fine.
  2. The good thing about using @Transactional on the method of a Junit test class is that Spring will automatically rollback the transaction after the test is done. So the above test method testSave() won't actually create a record in the database after the test. Below is from Spring documentation.

    In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener. Note that TransactionalTestExecutionListener is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must provide a PlatformTransactionManager bean in the application context loaded by @ContextConfiguration semantics. In addition, you must declare @Transactional either at the class or method level for your tests.

4. The configuration file app/src/test/resources/testApplicationContext.xml.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@myurl"
        p:username="myUsername" p:password="myPassword" p:initialSize="5" p:maxActive="10">
    </bean>
 
    <bean
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        id="entityManagerFactory">
        <property name="dataSource" ref="dataSource" />
    </bean>

 
    <context:component-scan base-package="com.example.dao">
    </context:component-scan>
 
    <bean class="org.springframework.orm.jpa.JpaTransactionManager"
        id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
 
   
    <context:spring-configured />
    <context:annotation-config />
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>
5. The file app/src/main/java/META-INF/persistence.xml:
   <persistence xmlns="http://java.sun.com/xml/ns/persistence"
 version="1.0">

 <persistence-unit name="MY-UNIT" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <non-jta-data-source>dataSource</non-jta-data-source>
  <properties>
   <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
   <!-- property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/ -->
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.hbm2ddl.auto" value="none" />
   <prop key="hibernate.connection.autocommit">false</prop>
   <prop key="hibernate.connection.release_mode">after_transaction</prop>
  </properties>
 </persistence-unit>

</persistence> 

Note that the output log will show that this persistence unit is used.

Question. The test needs to use the Entity class SampleData.java. How is this class discovered in the test context?

Answer: The document http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/configuration.html#setup-configuration-bootstrapping has good explaintion. It seems that by default, JPA will discover all the classes annotated with @Entity in the archive in the bootstrap process.

2.2.1. Packaging

The configuration for entity managers both inside an application server and in a standalone application reside in a persistence archive. 
A persistence archive is a JAR file which must define a persistence.xml file that resides in the META-INF folder. 
All properly annotated classes included in the archive (ie. having an @Entity annotation), all annotated packages and all 
Hibernate hbm.xml files included in the archive will be added to the persistence unit configuration, so by default, 
your persistence.xml will be quite minimalist:


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="sample">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

The actual command line of the test run is something like below:

C:\tools\jre-7u79-windows-x64\jre1.7.0_79\bin\javaw.exe -ea 
-classpath C:\Users\me\workspace\app\target\test-classes;
C:\Users\me\workspace\app\target\classes;
......
 -testLoaderClass org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader 
-loaderpluginname org.eclipse.jdt.junit4.runtime 
-classNames com.sample.dao.SampleDataDAOTest
Debugging of the program shows the following details. 1. The file testApplicationContext.xml has the bean entityManagerFactory of the class org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean. 2. When this bean is created, its method afterPropertiesSet() calls HibernatePersistence.createContainerEntityManagerFactory(), which calls Ejb3Configuration.buildEntityManagerFactory(), and so on. Eventually it will create a map that contains all the classes with the annotation @Entity. The last few classes used in this chain of call are JPA or Hibernate classes which are not related to Spring. So these classes must have the builtin ability to discover the @Entity classes by following the JPA standards.

Note that if you just use Hibernate without using the generic JPA,then you use the configuration file hibernate.cfg.xml. And you can list the entity classes or packages in that file as the following example shows.

  <hibernate-configuration>
     <session-factory>
       <property name="hibernate.connection.driver_class>"com.mysql.mysql.Driver</property>
       ......
       <mapping class="com.sample.SampleData"/>
     </session-factory>
   </hibernate-configuration>

1 comment:

  1. Thank you for your post. This is excellent information. It is amazing and wonderful to visit your blog.
    best ios training institute in bangalore
    ios app training in bangalore

    ReplyDelete