Thursday, February 15, 2007

Unit Testing JPA (EJB3) Code With With an EntityListener.

My goal was to verify that some objects were pulled from the EJB3 cache (ie, that I was actually getting cache hits) using unit tests. I did not see any way to get statistical information or cache information using the EJB3 API. However, I did read the about EntityListeners annotation.

@Entity
@EntityListeners( { DomainObjectListener.class })
public class DomainObject  {

The EntityListeners annotation allows you to specify a class which can track EJB3 events such as PrePersist and PostPersist. I developed some simple ObjectEvent classes to track, via a list, which EJB3 events happen. The following code shows my base class and one subclass. There is one subclass for each EJB3 event.

package org.domain;

abstract public class ObjectEvent {
   private IDomainObject domainObject = null;
   public ObjectEvent(final IDomainObject _domainObject) {
       super();
       this.domainObject = _domainObject;
   }
   public IDomainObject getDomainObject() {
       return this.domainObject;
   }
}

package org.domain;

public class PrePersistObjectEvent extends ObjectEvent {
   public PrePersistObjectEvent(final IDomainObject _domainObject) {
       super(_domainObject);
   }
}

I'm sure that you can create the rest of the ObjectEvent subclasses. The DomainObjectListener class holds a list of events and watched classes. The list of watched classes is needed because all of my persisted objects are subclasses of DomainObject. Here is the listener class:

package org.domain;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

public class DomainObjectListener {

   // private static final Logger logger = Logger.getLogger(DomainObjectListener.class.getSimpleName());

   final static private List watchedClasses = new ArrayList();

   @SuppressWarnings("unchecked")
   public static void addWatchedClass(Class clazz) {
       watchedClasses.add(clazz);
   }

   public static void clear() {
       watchedClasses.clear();
       events.clear();
   }

   final static private List events = new ArrayList();

   private static void addEvent(final ObjectEvent objectEvent) {
       events.add(objectEvent);
   }

   public static List getEvents() {
       return events;
   }
 
   public static int getNumberOfEvents() {
       return events.size();
   }
 
   public static boolean wasEventGenerated(final Class objectEventClazz) {
       boolean rv = false;
       for (ObjectEvent objectEvent : events) {
           if (objectEvent.getClass() == objectEventClazz) {
               rv = true;
               break;
           }
       }
       return rv;
   }

   public DomainObjectListener() {
       super();
   }

   @PrePersist
   public void prePersist(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PrePersistObjectEvent(item));
       }
   }

   @PreRemove
   public void preRemove(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PreRemoveObjectEvent(item));
       }
   }

   @PostPersist
   public void postPersist(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostPersistObjectEvent(item));
       }
   }

   @PostRemove
   public void postRemove(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostRemoveObjectEvent(item));
       }
   }

   @PreUpdate
   public void preUpdate(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PreUpdateObjectEvent(item));
       }
   }

   @PostUpdate
   public void postUpdate(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostUpdateObjectEvent(item));
       }
   }

   @PostLoad
   public void postLoad(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostLoadObjectEvent(item));
       }
   }

}

The unit tests to use the entity listener might look like this:

    public void test_new_object_pre_and_post_events_only() {
       DomainObjectListener.addWatchedClass(ContactCountry.class);
       addContactCountry("US", "United States of America");
       assertEquals(2, DomainObjectListener.getNumberOfEvents());
       assertTrue(DomainObjectListener.wasEventGenerated(PrePersistObjectEvent.class));
       assertTrue(DomainObjectListener.wasEventGenerated(PostPersistObjectEvent.class));
       DomainObjectListener.clear();
   }

I'm not happy with my solution because the entity listener class is assigned via an annotation which means that it is present in all enviroments - dev, test, and production - unless the DomainObject class is changed and recompiled during deployment. I'll keep looking for a better solution.

Post a Comment