Saturday, March 1, 2014

Unit Testing Java EE Application with CDI

Java Enterprise Edition has undergone the much needed weight loss treatment in 2006 when Java EE 5 was introduced and it has continued evolving ever since. Today, developing of Java enterprise applications is easier than ever before. However, the rising popularity of Test Driven Development creates new challenges even for the revamped Java EE platform.

Things have improved, of course. Prior to EJB 3.0, there was virtually no other way of testing enterprise beans than actually deploying them in the container and testing them there.

The EJB 3.0 returned back to the roots by turning the enterprise beans into Plain old Java objects (POJOs). This revolutionary idea brings many advantages, one of them is making EJBs readily available to unit testing. You only have to mock the dependencies to the container.

The question arises though, how do you unit tests those parts that directly depend on the container, like the Data Access Objects (DAOs) which use container provided persistence services?

In this post, I will show on an example of a RESTful Web Service project with database persistence how it can be tested end-to-end without ever hitting the application server.

The Project
The example project contains the following RESTful Web Service and DAO to put under (unit) test:
 @Path("/employees")  
 public class EmployeeResource {  
     @EJB  
     private EmployeeDao employeeDao;  
   
     @GET  
     @Path("/{param}")  
     @Produces(MediaType.APPLICATION_JSON)  
     public Employee get(@PathParam("param") long id) {  
         return employeeDao.find(id);  
     }  
     @GET  
     @Produces(MediaType.APPLICATION_JSON)  
     public List<Employee> findAll() {  
         return employeeDao.findAll();  
     }  
     @POST  
     @Consumes(MediaType.APPLICATION_JSON)  
     @Produces(MediaType.APPLICATION_JSON)  
     public Map<String, Long> add(Employee employee) {  
         Map<String, Long> response = new HashMap<>();  
         response.put("employeeId", employeeDao.create(employee));  
         return response;  
     }  
 }  
 @Stateless  
 public class EmployeeDao {  
     @PersistenceContext  
     private EntityManager em;  
   
     public Employee find(long id) {  
         return em.find(Employee.class, id);  
     }  
     public List<Employee> findAll() {  
         return em.createNamedQuery(Employee.FIND_ALL).getResultList();  
     }  
     public long create(Employee employee) {  
         em.persist(employee);  
         return employee.getEmployeeId();  
     }  
 }  
Injecting Dependencies
The above classes are richly annotated. A naive attempt to test the classes with JUnit will succeed to compile but it will fail at runtime with a NullPointerException due to unresolved dependencies to EmployeeDao and EntityManager. So is there a way to resolve these dependencies outside of the application server?
Enter CDI — context and dependency injection. CDI was added in Java EE 6 as a general dependency injection mechanism. With CDI it is possible to annotate references to EJBs with @Inject instead of @EJB:
 public class EmployeeResource {  
     @Inject  
     private EmployeeDao employeeDao;  
 ...  
This solves the DAO dependency nicely. However, the reference to the DAO will still be null because JUnit tests run with Java SE which does not support CDI. By now you can probably guess the central idea of the approach described in this post — to run the tests inside a CDI container instead of an application server. In the example project I use the JBoss Weld CDI container with Apache DeltaSpike which provides an abstraction layer to bootstrapping the container. You can inspect the project's Maven pom.xml file to see the actual library dependencies.
How about the dependency to the EntityManager though? It is defined as a private field in the DAO class and it has to be set outside of the application server. Is there some way to do it, short of breaking the encapsulation?
Actually, the solution is simple if you think of the famous aphorism by David Wheeler:

All problems in computer science can be solved by another level of indirection.

The indirection is implemented, of course, with CDI. The DAO uses an @Inject annotation instead of @PersistenceContext:
 public class EmployeeDao {  
     @Inject  
     private EntityManager em;  
 ...  
The definition of the entity manager in production/application server environment moves to a resource class which defines a CDI producer:
 public class Resources {  
     @PersistenceContext  
     @Produces  
     private EntityManager em;  
 }  
In the test environment an alternative resource class is used:
 @Alternative  
 public class TestResources {  
     @Produces  
     @Singleton  
     private EntityManager createEntityManager() {  
         EntityManagerFactory emf = Persistence.createEntityManagerFactory("hr_test");  
         return emf.createEntityManager();  
     }  
 }  
There are two things to notice about the above class:
  1. It is annotated with @Alternative. The alternative needs to be activated by specifying an <alternatives> tag in the beans.xml file. There are two beans.xml files in the example project — one in the production source code base (without <alternatives> tag so the persistence context is resolved using the Resources class) and another one in the test source code base.
  2. The above class creates an entity manager factory for the persistence unit hr_test. Alike beans.xml, there are also two persistence.xml files, for production and test respectively, in the example project.
Writing Test Classes
The test classes also use CDI to resolve the references to the classes being tested:
 @RunWith(CdiTestRunner.class)  
 public class EmployeeResourceTest {  
     @Inject  
     private EmployeeResource employeeResource;  
   
     @Test  
     public void testFindAll() {  
         List<Employee> employees = employeeResource.findAll();  
         assertThat(employees.size()).isGreaterThan(25);  
     }  
 }  
Bootstrapping the Container
Since the test classes are managed by CDI they need a custom JUnit test runner (specified with @RunWith) that has two responsibilities:
  1. Bootstrap the CDI container.
  2. Create instances of test classes using the CDI container.
 public class CdiTestRunner extends BlockJUnit4ClassRunner {  
     static {  
         CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();  
         cdiContainer.boot();  
     }  
     public CdiTestRunner(Class<?> clazz) throws InitializationError {  
         super(clazz);  
     }  
     @Override  
     protected Object createTest() throws Exception {  
         return BeanProvider.getContextualReference(getTestClass().getJavaClass());  
     }  
 }  
Adding Transaction Support
Transaction service is another service typically provided by the application server. In the standalone CDI test environment it is easy to add transaction support using an interceptor. The test methods are annotated with @Transactional and they can also specify whether the transaction should commit or rollback when the test finishes:
 @Test  
 @Transactional  
 public void testCreate() {  
     final long employeeId = 401L;  
     Employee employee = new Employee(employeeId, "PKANE", "PR_REP", "Kane", new Date());  
     assertThat(employeeDao.create(employee)).isEqualTo(employeeId);  
 }  
   
 @Test  
 @Transactional(defaultRollback = false)  
 public void testCreateCommit() {  
     final long employeeId = 402L;  
     Employee employee = new Employee(employeeId, "JTOEWS", "PR_REP", "Toews", new Date());  
     assertThat(employeeDao.create(employee)).isEqualTo(employeeId);  
 }  
 @InterceptorBinding  
 @Target({ElementType.METHOD, ElementType.TYPE})  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface Transactional {  
     boolean defaultRollback() default true;  
 }  
 @Interceptor  
 @Transactional  
 public class TransactionalRollbackInterceptor {  
     @Inject  
     private EntityManager em;  
   
     @AroundInvoke  
     public Object manageTransaction(InvocationContext ctx) throws Exception {  
         try {  
             em.getTransaction().begin();  
             return ctx.proceed();  
         } finally {  
             em.getTransaction().rollback();  
         }  
     }  
 }  
 @Interceptor  
 @Transactional(defaultRollback = false)  
 public class TransactionalCommitInterceptor {  
     @Inject  
     private EntityManager em;  
   
     @AroundInvoke  
     public Object manageTransaction(InvocationContext ctx) throws Exception {  
         em.getTransaction().begin();  
         Object result = ctx.proceed();  
         em.getTransaction().commit();  
         return result;  
     }  
 }  
Conclusion
That's it. If you want to check the entire example project's source code, you can find it in Subversion or download it as a zip archive.

Bonus Picture
As I was taking a break while writing the example project, our cat leapt on the table and took a sharp look on the source code, as if she was reviewing it. I was lucky enough to have my camera at my fingertips and I captured the moment:
Links

7 comments:

  1. Cool pictured. Thanks for the post. The new JEE is much better than J2E in my opinion. The POJOS make a big difference especially with unit testing. Of course, we all have to write our unit tests ;)

    ReplyDelete
    Replies
    1. Absolutely.. the point here is to make writing and running unit tests as delightful as possible. This means avoiding boilerplate code in the tests and having them run really fast. The CDI test approach addresses both these aspects.

      Delete
  2. do you have the sql to create the employees table? It may be better to use an in memory db in this example instead.

    ReplyDelete
  3. I like your example a lot! I think we are going to being testing in the fashion here! Currently, we make all the injected attributes with the default access modifiers and set the attribute using Mockito. It is truely unit but really skips all the annotations which are a big piece of the puzzle and should be tested with the rest of the class. We also have integration tests that launch Cargo but that seems a bit expensive to test each class. Here is a snippet of the sql to create the table. I switched to use mySql instead of Oracle:
    CREATE TABLE employees (
    COMMISSION_PCT DOUBLE,
    DEPARTMENT_ID BIGINT,
    email VARCHAR(20),
    EMPLOYEE_ID BIGINT,
    FIRST_NAME VARCHAR(20),
    LAST_NAME VARCHAR(25),
    PHONE_NUMBER VARCHAR(25),
    HIRE_DATE DATE,
    JOB_ID VARCHAR(20),
    death DATE,
    salary DOUBLE,
    MANAGER_ID BIGINT,
    PRIMARY KEY(EMPLOYEE_ID)
    );
    insert into employees (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, email) values (100, 'Joe', 'Smith', 'SKING');

    in your test you will need 25 more records for your unit test to pass (or change the assert in the unit test)

    ReplyDelete
    Replies
    1. Francisco, thanks for your comments and for posting the SQL! This is as you figured out from the Oracle HR schema.

      I fully agree with you that it would be better to use an in-memory database. In fact I have to admit that the test of the getAll method is flawed since it depends on pre-existing data in the database.

      The best practice for integration tests dealing with the database is to force the database into a known state before each test. Tools exist to create the known state, most notably DbUnit and DbSetup.

      Delete
  4. i am attempting to run your example with j2ee 6.0 but i keep on getting the following because the EntityMgr is failing to be injected by Weld. Could you provide any insight?

    WELD-000044 Unable to obtain instance from null

    ReplyDelete
    Replies
    1. I think the reason you get the error is that the project depends on default datasource (there is no jta-data-source definition in persistence.xml) which is a Java EE 7 feature (for more see https://blogs.oracle.com/arungupta/entry/default_datasource_in_java_ee).

      Please add a datasource to persistence.xml and try again.

      Delete