Sunday, August 23, 2009

Unit Test Java EE Code Outside Container

Java EE application depends a lots on the application server container for various resources like JNDI datasource, transaction manager etc. The application I am working on uses Hibernate as persistence layer. It should support transactions spanning multiple hibernate sessions.
Distributed transaction is piece of cake when the application runs in the container. Datasources can be configured in the application server and be bound to JNDI tree. UserTransaction is also available in the JNDI tree. Application code just uses the JTA interface, UserTransaction, for transaction demarcation.
Lets have a quick look at how this is done in the application code. Then we will see how to unit test this code layer, that participate in distributed transaction, outside the container.
  • Configure XA datasources in application server with JNDI name say ds1 & ds2. You can either use admin console provided by the application server or update server specific datasource configuration file.
  • Configure two hibernate session factory, one for each datasource.
one_hibernate.cfg.xml for datasource: ds1
two_hibernate.cfg.xml for datasource: ds2
Code in my Hibernate Util class:
SessionFactory sessionFactory = new Configuration().configure("/one_hibernate.cfg.xml").buildSessionFactory();
static_sessionFactory_Map.put("DS_ONE", sessionFactory);
sessionFactory = new Configuration().configure("/two_hibernate.cfg.xml").buildSessionFactory();
static_sessionFactory_Map.put("DS_TWO", sessionFactory);
  • Add transactional properties to hibernate.cfg.xml. I am using JBoss Application Server.
<property name="hibernate.current_session_context_class">jta</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>
  • Add transaction demarcation at business layer. Code in the business component typically would look like below
InitialContext ic = new InitialContext();
UserTransaction ut = (javax.transaction.UserTransaction) ic.lookup("UserTransaction");
try
{
ut.begin();
//invoke dao that uses ds1
//send xa jms messages
//run some other business logic
//invoke dao that uses ds2
//etc etc
ut.commit();
}
catch(SomeException ex)
{
ut.rollback();
}
So we are all set. Code will nicely handle distributed transactions! Now challenge here is: how to write junit tests to test this application outside the server container. How do we make JNDI datasources, UserTransaction, Transaction context available to the code? We need a standalone Transaction Manager. Junit tests need setup code to bind datasources and UserTransaction object. Lets go step by step.
  • Configure hibernate to use Bitronix Transaction Manager
<property name="hibernate.current_session_context_class">jta</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="hibernate.transaction.manager_lookup_class">custom.lookup.manager.see.bitronix.doc</property>
  • Use Apache Naming in-memory JNDI service provider to bind the resources
Setup code in junit tests:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ic = new InitialContext();
//create and bind datasources. We are using Oracle database server
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setURL("jdbc:oracle:thin:@host:port:sid1");
ds.setUser("username");
ds.setPassword("password");
ic.rebind("ds1", ds);
ds = new OracleConnectionPoolDataSource();
ds.setURL("jdbc:oracle:thin:@host:port:sid2");
ds.setUser("username");
ds.setPassword("password");
ic.rebind("ds2", ds);
//we are using bitronix.tm.TransactionManagerServices
UserTransaction userTransaction = TransactionManagerServices.getTransactionManager();
ic.rebind("UserTransaction", userTransaction);
  • All set! Just write junit tests to test business methods
public void testBuzApi()
{
MyBizClass bizObj = new MyBizClass();
SomeObject retObj = bizObj.myMethodOne();
//some assertXXX(.....);
}
Looks like its not a challenging task to unit test java EE code, right. We just need to provide all the resources application code expect while running in a server container.

2 comments:

Anonymous said...

Would you zip your project for us to try?

adey said...

Hi There,

Thanks for leaving the first comment on my blog! I will try to put together test codes as a small project whenever I get some free time. Meanwhile, please let me know if you have any specific questions.

Thanks