Thursday, July 16, 2009

Getting Started with MockEJB

I spent a little time recently trying to get a lightweight framework for some basic EJB unit testing with the goal in mind to reduce the number of times that I had to deploy to the application server to test new code.  I had been using MockRunner for a while to test tag classes, so I thought I would try to use the EJB component which uses MockEJB as a base for testing.

The hardest part of the whole thing was really figuring out how to get the finders to work, but as usual, I was likely making it a bit harder than I had to, this is how I did it.

MyEJBTestCaseAdapter

MockRunner provides a base class for doing an EJB Test Case called EJBTestCaseAdapter, the setUp() method does pretty much everything to do with setting up your context and creating the basic environment, all you have to do is deploy your EJB classes.  The base class has methods for deploying both entity and session interfaces, but I found that the deploy(BasicEjbDescriptor) method worked best for me, giving me the flexibility to deploy local and remote interfaces from multiple packages.  The deploySession and deployEntity methods the the MockRunner class just provide a wrapper for the deploy method to reduce some of the complexity.  So as long as you don’t have a complicated project, they should work fine for you.

I extended this test case adapter class to make my life a little easier and implemented my own deploy methods since my entity and session classes are in different packages.  This just makes life a little easier because now I just have to use deployMyEntity(“Tblwhatever”):

  1: protected void deployMyEntity(String name) throws Exception
  2: {
  3:     final String interfaceName = "my.entity.package." + name;
  4:     ejbModule.deploy( new EntityBeanDescriptor(
  5:         "java:comp/env/ejb/local/" + name,
  6:         Class.forName(interfaceName + "LocalHome"),
  7:         Class.forName(interfaceName + "Local"),
  8:         Class.forName(interfaceName + "Bean")));
  9: }


Sessions are a little bit more complicated because our naming convention for Remote and Local Interfaces are a bit different:



  1: protected void deployMySession(String name, boolean local) throws Exception
  2: {
  3:     final String myPackage = "my.session.package.";
  4:     final String interfaceName = myPackage + name;
  5:     final String jndiName = name;
  6:         
  7:     if ( local )
  8:     {
  9:         jndiName = "java:comp/env/ejb/local/" + name;
 10:         interfaceName = interfaceName + "Local";
 11:     }
 12:       
 13:     ejbModule.deploy( new SessionBeanDescriptor(
 14:         jndiName,
 15:         Class.forName(interfaceName + "Home"),
 16:         Class.forName(interfaceName),
 17:         Class.forName(myPackage + name + "Bean")), TransactionPolicy.REQUIRED);
 18: }


Both of these classes make reference to an ejbModule which are provided by a method in the EJBTestCase Adapter.  I set this up in my setUp() method:



  1: protected final EJBTestModule ejbModule;
  2: protected final JDBCTestModule jdbcModule;
  3: 
  4: @Override
  5: public void setUp() throws Exception
  6: {
  7:     super.setUp();
  8:         
  9:     jdbcModule = createJDBCTestModule();
 10:     ejbModule = createEJBTestModule();
 11:     ejbModule.bindToContext("jdbc/MyDS", 
 12:         getJDBCMockObjectFactory().getMockDataSource());
 13: }


Creating a Test



My TestCase simply extends this class where I deploy the sessions and entities that I will need during my test.  For simplicity, I have a simple entity that has an id field that I want to update



  1: public class MyTest extends MyEJBTestCaseAdapter
  2: {
  3:   @Override
  4:   public void setUp() throws Exception
  5:   {
  6:     super.setUp();
  7: 
  8:     deployMySession("MySessionEJB", false);
  9:     deployMyEntity("Tblsomething");
 10:   }
 11: 
 12:   public void testSomething() throws Exception
 13:   {
 14:     final MySessionEJB session = EjbHomeFactory.lookup("MySessionEJB");
 15:     session.updateSomething(1L);
 16: 
 17:     final Tblsomething t = findByPrimaryKey(1L);
 18:     assertEquals(1L, t.getId());
 19:   }
 20: }


Dealing with Aspects



Assuming that your session just creates the Tblsomething entity, you likely will not have a problem running this test.  But if you are looking up an entity, you will have to implement an Aspect that will intercept your finder and return something for you to play with.  Here is a simple Aspect that intercepts a findByPrimaryKey method and returns a new entity that is probably suitable for a simple test:



  1: class FinderHandler implements Aspect
  2: {
  3:     FinderHandler(String entity, String finderMethod, Object[] createParams, Object pk)
  4:     {
  5: 	targetEntity = entity;
  6: 	targetFinder = finderMethod;
  7: 	entityToReturn = 
  8:           createEntityBean("java:comp/env/ejb/local/" + entity, 
  9:              createParams, pk);
 10:     }
 11: 
 12:     private final String targetEntity;
 13:     private final String targetFinder;
 14:     private final Object entityToReturn;
 15: 
 16:     public Pointcut getPointcut()
 17:     {
 18: 	return new MethodPatternPointcut(
 19:                targetEntity + ".*" + targetFinder);
 20:     }
 21: 
 22:     public void intercept(InvocationContext invocationContext)
 23:     {
 24: 	invocationContext.setReturnObject(entityToReturn);
 25:     }
 26: }


This aspect class will basically create an entity that you define using the parameters of your create method as an Object[].  The EJBTestCaseAdapter class provides this handy utility for creating an entity by looking up the class and using Reflection to find the create method and invoking it and then persisting the entity in an in memory data source that can then be retrieved using the findByPrimaryKey(Object) method in your test case.



Conclusion



Obviously, this kind of test does nothing to attempt to test the actual EJB platform with all of its intricate dependencies and configurations, but it does allow a developer to execute EJB code on their development workstations and apply TDD principles when developing EJB code.

No comments: