July 29, 2009

Effective Unit Testing EJB 3.0 with OpenEJB

In my latest project we used EJB 3.0 and Maven2. And as any good programmer I wanted to have good code quality, which means good unit test test coverage, but my expectation on how to achieve this in an efficient manner was quite low, since mine previous experience with EJB 2.1 did not include any good testing experience. Even if coding EJB 3.0 has been greatly simplified, the fact still remains that the enterprise bean has still to be deployed before it can be tested. Of course can one program such that the logic is in a POJO rather directly in the bean itself, but to really be sure it is working the enterprise bean has to be deployed in a container, to be testable. So I started looking at different Maven plugin such as, jboss-maven-plugin and cargo-maven2-plugin, but then came across OpenEJB, which is really the salvation if you are developing EJB 3.0 and want good testing possibility. OpenEJB enable unit testing of enterprise beans as any POJO class! Lets look at some examples.
public class UserServiceSLSBTest {
private static final String EXPECTED_USERNAME_1 = "fornamn.1.efternamn@domain.com";
private static final String EXPECTED_USERNAME_2 = "fornamn.2.efternamn@domain.com";
private static final String EXPECTED_USERNAME_3 = "fornamn.3.efternamn@domain.com";
private Context ctx;

@BeforeClass
public static void oneTimeSetUp() {
}

@AfterClass
public static void oneTimeTearDown() {
}

@Before
public void setUp() throws Exception {
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");

props.put("blogUnmanaged", "new://Resource?type=DataSource");
props.put("blogUnmanaged.JtaManaged", "false");
props.put("blogUnmanaged.JdbcDriver", "com.mysql.jdbc.Driver");
props.put("blogUnmanaged.JdbcUrl", "jdbc:mysql://localhost:3306/blog");
props.put("blogUnmanaged.username", "root");
props.put("blogUnmanaged.password", "");
// TODO fix properties hibernate.hbm2ddl.auto=create-drop

ctx = new InitialContext(props);
}

@After
public void tearDown() {
}

private UserServiceLocal createUserService() throws Exception {
Object obj = ctx.lookup(UserServiceSLSB.class.getSimpleName() + "Local");

assertNotNull(obj);
assertTrue(obj instanceof UserServiceLocal);
return (UserServiceLocal) obj;
}

@Test
public void testCreateUser() throws Exception {
UserServiceLocal userService = createUserService();

User user = new User();
user.setUsername(EXPECTED_USERNAME_1);
user.setPassword(EXPECTED_USERNAME_1);
user.setRoles(Roles.ADMIN);

userService.storeUser(user);
}
}


And the Stateless Session Bean

@Stateless
public class UserServiceSLSB implements UserServiceLocal {
private static final Logger log = LoggerFactory.getLogger(UserServiceSLSB.class);
@PersistenceContext(name = "blog-ejb", unitName = "blog-ejb")
protected EntityManager em;
@Resource
private SessionContext context;

@Override
public User findUserByUsername(String username) {
log.info("findUserByUsername username='" + username + "'...");
Query q = em.createQuery("SELECT u from User as u where u.username=:username");
q.setParameter("username", username);
List list = q.getResultList();
return (list.size() > 0) ? (User) list.get(0) : null;
}

private void checkExistingUser(User user) throws DuplicateException {
User existingUser = findUserByUsername(user.getUsername());
if (existingUser != null)
throw new DuplicateException("Duplicate " + user);
}

@Override
public void storeUser(User user) throws CredentialException, DuplicateException {
log.info("storeUser " + user + "...");
checkExistingUser(user);
if (user.getUserId() == null)
em.persist(user);
else
em.merge(user);
}
}


And the SLSB local interface.

@Local
public interface UserServiceLocal {

public User findUserByUsername(String username);

public void storeUser(User user) throws CredentialException, DuplicateException;
}


And in the pom.xml simply add the dependency to OpenEJB and you are ready to go.

<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>

7 comments:

dlichtenberger said...

I absolutely second the recommendation for OpenEJB 3. We (www.flexive.org) use it for both testing (with Maven, but Ant is easy too) and also for deployment (Jetty+OpenEJB = small footprint and almost no startup time).

Daniel

Haiyao said...

Can you show your entity User class?
I found the entity classes in openejb examples are not valid. for example, in the Testing Transactions, the Movie class:
@Entity
public class Movie {

private String director;
private String title;
private int year;

public Movie() {
}
...

it does not implements Serializable, and no @Id either!
If I add @Id private Long id;
and the testWithTransaction failed with:
WARN - Unexpected exception from beforeCompletion; transaction will roll back
<openjpa-1.2.0-r422266:683325 nonfatal store error> org.apache.openjpa.persistence.OptimisticLockException: Optimistic locking errors were detected when flushing to the data store. The following objects may have been concurrently modified in another transaction: [org.superbiz.injection.tx.Movie@1220b36, org.superbiz.injection.tx.Movie@2af8f5, org.superbiz.injection.tx.Movie@e0f0ad]
at org.apache.openjpa.kernel.BrokerImpl.newFlushException(BrokerImpl.java:2160)
...

Haiyao

ferJON said...

Hi,

I tried running the example project "jpa-hibernate" from openejb. The records in the test method were not persisted in the database after testing so I assumed this would be in_memory only. How do I change the it in order for the test records to be persisted in the database?

In ejb3unit, I recall something like ejb3unit.inMemoryTest=false. Does openejb also provide this mechanism in testing ejbs?


Thanks,

Magnus K Karlsson said...

The Entity bean User:

@Entity
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
@Column(length = 250)
private String username;
@Column(length = 250)
private String password;

// and then follows the getter- and setter method.

}

The reason I made the Entity bean serializable is because I use it in the web layer.

Jalel said...

Hello, I am new in the world of java and especially maven and open ejb, I checked your blog and I found that you use maven with openejb for unit test EJBs, I'm stuck in the tests and I want ask you it's that can I use two files persistence.xml or two persistence unit? If yes how can I do this to test my EJBs. Thank you.

Magnus K Karlsson said...

What I guess you want is to mock the data connection. That is done JUnit setUp() method

props.put("blogUnmanaged", "new://Resource?type=DataSource");

here you alter the datasource reference made in the persistence.xml, to your local database or in-memory db.

Another approach is two use persistence.xml files, one in src/main/resource and the other in src/test/resource and in your JUnit class manually load your test persistence unit

Persistence.createEntityManagerFactory("idInThePersistence.xml").createEntityManager

and the manually inject that in your EJB.

I hope that answer your question.

Javier Escobar said...

Hi...

Thank you so much for the tutorial, it's very useful.

However, i am trying to do it in a school project. I have this entity:

@Entity
@Table(name = "Media", uniqueConstraints = {
@UniqueConstraint(columnNames = {"location"}),
@UniqueConstraint(columnNames = {"name"})})
public class Media implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idMedia", nullable = false)
private Integer idMedia;
@Basic(optional = false)
@Column(name = "name", nullable = false, length = 45)
private String name;
(...)

With a session bean that persists in a similar way of the tutorial. The test case is similar too.

But I have the following exception:

org.apache.openjpa.persistence.InvalidStateException: The field "idMedia" of instance "com.lindecore.dataaccess.entities.Media[idMedia=null]" contained a null value; the metadata for this field specifies that nulls are illegal.

When I deploy this, it works, but when I execute the tests, it fails :S.

do you think that the error is due to the GenerationType = IDENTITY??? or something else??

Thanks!!