I would like to present a new approach on testing Java EE Applications. Testing is a critical aspect in any application. I think that’s a given and that’s why I’m not going to dive into why you should always test your applications.

There are several types of tests: unit tests, integration tests, UI tests. This new approach lies between unit and integration tests and I think, it takes the best of both. That means, you can test your application with its dependencies and get feedback almost as fast as a unit test without having to mock your whole application. I believe this is the most  compelling argument. You can see this approach as unit testing with super powers!

As you may have noticed, I am talking about an approach and not a Framework per se. This approach “stands on the shoulder of giants” because It merely uses some powerful frameworks and their features to achieve its purpose and this is where CDI comes in.

CDI stands for Context and Dependency Injection. It is a specification like JPA,and is part of Java EE 6, which means, it is available out-of-the-box on all Java EE6 application servers. I think the name of the spec doesn’t do justice to the functionality it provides. In addition to dependency injection and context (scopes) there are some features that make CDI quite powerful like interceptors, decorators, producers, events and extensions. In my opinion CDI extensions are a very powerful yet underused feature. They provide a way to portably extend your application server by modifying the metadata of the beans inside your application. This testing approach relies heavily on CDI extensions but don’t worry, we just need one CDI extension and it is quite small.

Now we know that we need a CDI Container. Like JPA, there are a couple of CDI implementations out there with JBoss Weld being the reference implementation. For this approach we will use Weld SE. As its name implies, Weld SE can run on pure Java SE. Weld SE scans your classpath when it starts up and searches for CDI components like beans, interceptors, events and extensions. It works as if you were deploying your application on a Java EE6 application server. However, Weld SE is not directly used. We use the CDI container abstraction from Apache Deltaspike. Actually, we use a couple of nice features from Deltaspike to change the Beans’ metadata (more about that later).

The idea of using CDI for testing purposes is not new actually. You can find several examples on the Internet about how to test applications that solely uses CDI. However, we want to test a Java EE Application. Since you mostly have EJBs in a Java EE application, using CDI alone won’t help you with that. We need to help the CDI container a little bit so it can resolve EJBs. You might think that would be crazy but I think it is forward thinking actually. The Java EE expert group is seriously considering to get rid of EJBs as we currently know them and use CDI stereotypes instead. So @Stateless would become a CDI stereotype that includes the typical EJB services like transactions, security and pooling.

This approach goes in that direction by changing the beans’ meta data and thus “converting” EJB into CDI beans. Basically a stateless EJB is a bean that per default is both request scoped and transactional. With that in mind, the following CDI extension changes the metadata of every stateless EJB by adding @RequestScoped and @Transactional.

As you can see, the actual work happens in the method modifyAnnotatedTypeMetaData. By using some nice utilities from Deltaspike, like AnnotationInstanceProvider and AnnotatedTypeBuilder, we are able to change the bean’s metadata very straightforward. Basically the @RequestScoped and @Transactional annotations were added to the bean class. The @Inject annotation was also added at every @EJB injection point, thus converting @EJB into @Inject so that the CDI container can resolve the dependencies.

As you may have noticed, the @Transactional annotation is not part of CDI. This annotation is merely a CDI interceptor binding. So, again, we are using another CDI feature to “enhance” the beans. Where there is an interceptor binding, there is an interceptor. In this case, it is called TransactionalInterceptor and it provides basic transaction propagation. In this way, if an EJB starts a transaction and then calls another EJB, the transaction is propagated to that  EJB. As usual, you need to enable the interceptor in the beans.xml file:

We use another CDI feature for the whole thing to work: producer methods. Normally in a Java EE environment, the EntityManager gets injected when the application server finds the @PersistenceContext annotation. The application server takes care of the initialization and synchronization of the persistence context, etc. Since we are running the tests outside the application server, we need to be able to initialize the EntityManager before it gets injected. To do this, a so called EntityManagerProducer is used. The Producer method getEntityManager will be called by the CDI container when an instance of an EntityManager is required. Here you can find more information about producer methods.

 

Once again, we use standard stuff like JPA with local transactions, so that we can handle the transactions outside the application server. There is actually nothing new with this initialization. This is how I usually test the Persistence Layer of any Java EE application regardless whether using CDI or not.

How does a “Bean Test” look like? Well, it looks a lot like a normal Unit test (and it behaves quite similarly too):

You can see that the test extends BaseBeanTest. This super class initializes and shuts down the CDI Container.

 

And yes, you guessed it right. The BeanProviderHelper is where all the action happens, well, it actually delegates the action to Deltaspike.

 

What about dependencies to other modules or services?

Usually, there are several dependencies to other services in a big Java EE application. Normally you have an interface to reference that dependency but you have no access to the implementation (at least not in the classpath of the module you want to test). The problem with this is that the CDI container will try to resolve the dependency and it will end up in an error because it cannot find a suitable implementation. The solution is a producer method that provides mocks for those services.

 

We use Mockito to create the mocks and thanks to the producer method, the CDI container is able to resolve the injection point for the external dependency. Since the external service is mocked with mockito, you can initialize it the usual way to fit your testing purposes

I hope that at this point you start getting the idea, that almost every “problem” that occurs when using this approach can be solved with a CDI feature. So you start to see why CDI is so powerful and flexible and why the Java EE expert group is considering to expand CDI overall in the Java EE spec.

Does “Bean testing” actually work in real life projects?

Yes, it does and quite well. I have been using “Bean Testing” in several customer and internal projects for over a year with noticeable gains in feedback speed and test coverage. The bigger the project, the bigger the benefits. Big Java EE projects usually consist of several modules and this modularization makes a little hard to hot deploy and test the project. Besides there are still a couple of Java EE application servers out there that need a lot of configuration and time to properly deploy an application. So this approach really shines with those kind of projects. However this approach also works pretty well for small and medium size Java EE projects. It is just a one-time setup and you are ready to go.

Try it and give some feedback

I encourage you to give it a try. You can find the whole project in GitHub under: https://github.com/NovaTecConsulting/BeanTest

The best way to integrate this approach to your current project is to get the code and add the dependency to your test classpath (i.e. pom.xml with test scope). Then provide a persistence unit called “beanTestPU”. The persistence unit can be created under src/test/resources/META-INF . After that write a simple test that tries to call an EJB Method and see if all dependencies can be resolved when running the tests. If that is not the case, you can always mock or provide an (dummy) implementation for the dependency.

Dependencies and project structure

The project is built with maven. When you clone the project and run mvn clean install, it will create a jar containing only the classes under src/main/java. Classes like  ExternalServicesMockProducer will not be delivered in the jar. That is done on purpose because normally the persistence artifacts and the external services are dependent on the actual project being tested. One of the main objectives and advantages of this approach is that your production code should remain unmodified. That’s why you find the persistence.xml under src/test/resources instead of src/main/resources. By doing this, your production persistence.xml remains untouched and you can then create a persistence context suitable to your testing needs (i.e. in-memory database).

Updated on 10.03.2014: EntityManagerProducer is actually delivered, so there is no need to implement one.

Leave a Comment

2 comments

  1. J

    Hi,

    I came across your post while looking for a way to unit-test a C.D.I.-based application. I’ve started to implement the solution you describe, and everything works fine so far.

    However, I’d like to improve my persistence tests by using tools like DbUnit or DbSetup, and I can’t see a proper way to make them work with the Bean Test framework. For instance, I cannot see how I can retrieve the DataSource object from the memory database used in the tests. Is it even possible?

    Regards.

  2. C

    Hallo Jean,

    CDI alone cannot create a DataSource. A DataSource resource is usually provided by the container (Application Server). However you can write a method annotated with @Produces to create your own DataSource. Please take a look at this Gist:
    https://gist.github.com/carlosbarragan/13f5b47d7989e503db19e3f32ab41747

    Regards,
    Carlos

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close