Portlet Unit Testing with Liferay 6

>> Tuesday, June 8, 2010

My Test Obsession
I must admit, I am a bit obsessed with automated tests. Whenever I approach the development of a new project with a new system, one of my main concerns is how can we implement and run unit tests? So when I tackled the development of a Liferay portlet I assumed that a unit testing framework or practice was a given, Surely someone (if not the Liferay folks) had mastered and documented this aspect of portlet development. Well, Googling and searching for this revealed that no one seemed to have documented a simple and elegant solution to this need. It is easy to test the base Liferay portlets that can be found in the Liferay SDK and source. However, running Unit tests for a portlet in a separate Eclipse project poses a few challenges as mentioned in the following Liferay forum article: http://www.liferay.com/community/forums/-/message_boards/message/3827878.

The solution proposed in that article did not satisfy me and appeared too complex. There had to be a better and simpler way. After some tinkering and reverse engineering using the Eclipse debugger and changing the log4j settings, I can now run a suite of unit tests for my Liferay service including persistence. The following describes how to achieve this and the general approach. You will probably need to adapt the example to your own situation/portlet but the general approach should be applicable. I am still somewhat green when it comes to Liferay and my understanding of the inner workings of the solution is far from complete so there may be better ways of doing this. Please let me know if you find something missing or incorrect so I can improve this documentation.

The main difficulty with portlet unit testing, from what I observed, is due to the fact that portlets are meant to run inside (or alongside) a portal which provides a number of resources and contexts that are not de facto present when running unit tests. Fortunately, Liferay is based on Spring and the Liferay folks did a good job of making it easy for us to extend or override the context in which code is executed.

In the following example, we have a relatively simple portlet in which Java code was essentially generated with the "build-service" Ant script based on a WEB-INF/service.xml file. What we basically need to do to run the unit tests is create a new source folder for the project that will contain the test resources. This folder contains resources such as Java classes, property and Spring context files that are used exclusively for unit testing and must not be included in the WAR bundle to be deployed in the portal container.

My JUnit test class extends com.liferay.portal.service.persistence.BasePersistenceTestCase and implements the following methods:

public abstract void testCreate() throws Exception;
public abstract void testRemove() throws Exception;
public abstract void testUpdateNew() throws Exception;
public abstract void testUpdateExisting() throws Exception;
public abstract void testFindByPrimaryKeyExisting() throws Exception;
public abstract void testFindByPrimaryKeyMissing() throws Exception;
public abstract void testFetchByPrimaryKeyExisting() throws Exception;
public abstract void testFetchByPrimaryKeyMissing() throws Exception;
public abstract void testDynamicQueryByPrimaryKeyExisting()
      throws Exception;
public abstract void testDynamicQueryByPrimaryKeyMissing() throws Exception;

I am a lazy type so I simply copied an existing subclass of BasePersistenceTestCase and changed the implementation so it refers to my own services.

In the root of the source test folder, put a file called portal-test.properties containing the following (substitute $TOKEN$ to reflect your context):

jdbc.default.driverClassName=$DRIVER_CLASS_NAME$
jdbc.default.url=$DB_URL$
jdbc.default.username=$USER_NAME$
jdbc.default.password=$USER_PASSWORD$

# Disable the scheduler for Unit testing
ehcache.portal.cache.manager.jmx.enabled=false

value.object.listener.com.liferay.portal.model.LayoutSet=

resource.repositories.root=$RESOURCE_REPOSITORIES_ROOT$

# Disable the scheduler for Unit testing
scheduler.enabled=false

hibernate.configs=\
META-INF/mail-hbm.xml,\
META-INF/portal-hbm.xml,\
META-INF/ext-hbm.xml,\
META-INF/portlet-hbm.xml

One of the basic principles in Liferay is that you can override base properties and Spring configs with your own. In the above file, we define properties that override the base ones that come from the portal project.

In the same fashion, I created a "ext-spring.xml" file in the META-INF subfolder of the source test folder that overrides the bean definitions found in the portlet or portal spring contexts. As an example, the content of the "ext-spring.xml" file I use is the following (substitute type names to reflect your service/portlet):

<beans default-destroy-method="destroy" default-init-method="afterPropertiesSet"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
 xsi:schemalocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <bean
  class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"
  id="liferayDataSource">
  <property name="targetDataSource">
   <bean class="com.liferay.portal.dao.jdbc.util.DataSourceFactoryBean">
    <property name="propertyPrefix" value="jdbc.default.">
    </property>
   </bean>
  </property>
 </bean>

 <bean class="com.opnworks.portlet.iamhere.service.impl.MyLocalServiceImpl"
  id="com.opnworks.portlet.service.MyLocalService">
  <bean class="com.opnworks.portlet.service.MyLocalServiceUtil" id="com.opnworks.portlet.service.MyLocalServiceUtil">
   <property name="service"
    ref="com.opnworks.portlet.service.GeoCampaignLocalService">
   </property>
  </bean>
  <bean
   class="com.opnworks.portlet.iamhere.service.persistence.MyEntityPersistenceImpl"
   id="com.opnworks.portlet.service.persistence.MyEntityPersistence"
   parent="basePersistence">

   <bean
    class="com.liferay.portal.spring.transaction.TransactionManagerFactory"
    factory-method="createTransactionManager" id="liferayTransactionManager">
    <constructor-arg ref="liferayDataSource">
     <constructor-arg ref="liferayHibernateSessionFactory">
     </constructor-arg>
    </constructor-arg>
   </bean>
   <bean
    class="com.opnworks.portlet.iamhere.service.persistence.PortletHibernateTestConfiguration"
    id="liferayHibernateSessionFactory">
    <property name="dataSource" ref="liferayDataSource">
    </property>
   </bean>
   <bean class="com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil"
    id="com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil">
    <property name="dynamicQueryFactory">
     <bean
      class="com.liferay.portal.dao.orm.hibernate.DynamicQueryFactoryImpl">
     </bean>
    </property>
   </bean>
   <bean class="com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil"
    id="com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil">
    <property name="restrictionsFactory">
     <bean
      class="com.liferay.portal.dao.orm.hibernate.RestrictionsFactoryImpl">
     </bean>
    </property>
   </bean>
   <bean class="com.liferay.portal.kernel.util.InfrastructureUtil"
    id="com.liferay.portal.kernel.util.InfrastructureUtil">
    <property name="dataSource" ref="liferayDataSource">
     <property name="transactionManager" ref="liferayTransactionManager">
     </property>
    </property>
   </bean>
  </bean>
 </bean>
</beans> 
                                                     

In the above file, the main trick that I pulled is to redefine the bean called liferayHibernateSessionFactory and to specify the following class implementation:

import com.liferay.portal.spring.hibernate.PortletHibernateConfiguration;

public class PortletHibernateTestConfiguration extends
  PortletHibernateConfiguration {
 protected ClassLoader getConfigurationClassLoader() {
  return this.getClass().getClassLoader();
 }
}

This was needed because the PortletHibernateConfiguration implementation throws a NPE on the getConfigurationClassLoader() method invocation due to the fact that we are not running inside a container. I also put the above class definition in my test source folder. If anybody can suggest a more elegant solution, I would be delighted to hear about it.

Another useful trick is to put a copy of portal-log4j.xml in the META-INF subfolder of your test source folder. That way, you can change the logging strategy used for unit testing.

If using Eclipse, you can simply run your unit test classes or suite as a JUnit configuration.

Have fun in the Liferay lane...

Read more...

  © Blogger template Webnolia by Ourblogtemplates.com 2009

Back to TOP