Unit Testing Strategies for CDI based projects

Even after 2 years of CDI going public there are still frequent questions about how to unit test CDI based applications. Well, here it goes.

1. Arquillian, the heavyweight champion

Arquillian is the core part of the EE testing effort of JBoss. The idea is to write a unit test exactly once and run it on the target platform via some container integration connectors.

How does Arquillian tests look like?

I will not give a detailed introduction here, but I’d like to mention that an Arquillian test will usually contain a @Deployment section which packages all the classes under test into an own temporary Java packaging, like a JAR, EAR, WAR, etc and uses this temporary artifact to run the tests.

There are basically 2 ways to run Arquillian tests. I’ll not go into details but only give a very rough (and probably not too exact) overview:

Remote/Managed style Arquillian tests

In this case the Arquillian tests will be started in an extenal Java VM, e.g. a started JBossAS7 or GlassFish-3.2. If you start your unit test locally, it will get ‘deployed’ to the other VM and executed over there. You can debug through as if you were working over there which is established via the java debugging and profiling API. Some container connectors also allow to locally run ‘as-client’.

The Good: this is running exactly on the target platform you do your real stuff on.
The Bad: Slower than native unit tests. No full control over the lifecycle, etc. You e.g. cannot simulate multiple servlet requests in one unit test.

Embedded style Arquillian tests

Instead of deploying your Arquillian test to another VM you are starting a standalone CDI or embedded EE container in the current Java VM.

The Good: Much faster as going remote. And you don’t need any server running.
The Bad: Sometimes this leads to ClassLoader isolation problems between your @Deployment and your unit test. This leads to problems when you e.g. like to unit test classes which starts an EntityManager. The reason is that e.g. any persistence.xml will get picked up twice. Once from your regular classpath and the second time from the @Deployment.

You can read more about it in the JBoss Arquillian documentation (thanks to Aslak Knutsen for the links):

2. DeltaSpike CdiControl, the featherweight champion

The method to unit test CDI based programs I like to introduce next uses the Apache DeltaSpike CdiControl API introduced in my previous post. It is based on the ideas and experience I had with the Apache OpenWebBeans CdiTest module I created and heavily use since 3 years.

First of all, you will need a META-INF/beans.xml in your test class path. All your unit tests will at the end become CDI enabled classes! If you use maven, then just create an empty file.

$> touch src/test/resources/META-INF/beans.xml

Instead of creating a @Deployment like Arquillian does, we now just take the whole test-dependencies and scan them. Of course this means that starting the CDI container for a unit test will always have to scan all the classes which are enabled in JARs which have a META-INF/beans.xml marker file. But on the other hand we spare creating the @Deployment and always test the ‘real thing’.

Introducing the test base class

The first step is to create a base-class for all your unit tests. I will break down the full class into distinct blocks and explain what happens. I’m using testng in this case, but jUnit tests will look pretty similar.

The main thing we need is the DeltaSpike CdiContainer. CDI containers don’t like it to run concurrently within the same ClassLoader. Thus we have our CdiContainer as static member variable and share it across parallel tests.

public abstract class CdiContainerTest {
    protected static CdiContainer cdiContainer;

Now we also need to initialize and use it for each method under test. This is also a good place to set the ProjectStage the test should execute in.
After boot()-ing the CdiContainer we also start all the contexts.
If the container is already started, we just clean the contextual instances for the current thread.

    @BeforeMethod
    public final void setUp() throws Exception {
        containerRefCount++;

        if (cdiContainer == null) {
            ProjectStageProducer.setProjectStage(ProjectStage.UnitTest);

            cdiContainer = CdiContainerLoader.getCdiContainer();
            cdiContainer.boot();
            cdiContainer.getContextControl().startContexts();
        }
        else {
            // clean the Instances by restarting the contexts
            cdiContainer.getContextControl().stopContexts();
            cdiContainer.getContextControl().startContexts();
        }
    }

We also do proper cleanup after each method finishes. We especially cleanup all RequestScoped beans to ensure that e.g. disposal methods for @RequestScoped EntityManagers gets invoked.

    
    @AfterMethod
    public final void tearDown() throws Exception {
        if (cdiContainer != null) {
            cdiContainer.getContextControl().stopContext(RequestScoped.class);
            cdiContainer.getContextControl().startContext(RequestScoped.class);
            containerRefCount--;
        }
    }

Another little trick: in the @BeforeClass we ensure that the container is booted and then do some CDI magic. We get the InjectionTarget and fill all InjectionPoints of our very unit test subclass via the inject() method.
This allows us to use @Inject in our unit test classes which extend our CdiContainerTest.

    @BeforeClass
    public final void beforeClass() throws Exception {
        setUp();
        cdiContainer.getContextControl().stopContext(RequestScoped.class);
        cdiContainer.getContextControl().startContext(RequestScoped.class);

        // perform injection into the very own test class
        BeanManager beanManager = cdiContainer.getBeanManager();

        CreationalContext creationalContext = beanManager.createCreationalContext(null);

        AnnotatedType annotatedType = beanManager.createAnnotatedType(this.getClass());
        InjectionTarget injectionTarget = beanManager.createInjectionTarget(annotatedType);
        injectionTarget.inject(this, creationalContext);
    }

After the suite is finished we need to properly shutdown() the container.

    @AfterSuite
    public synchronized void shutdownContainer() throws Exception {
        if (cdiContainer != null) {
            cdiContainer.shutdown();
            cdiContainer = null;
        }
    }

}

You can see the full class in my lightweightEE sample (work in progress):
CdiContainerTest.java

The usage

Just write your unit test class and extend the CdiContainerTest class.

public class CustomerServiceTest extends CdiContainerTest
{

You can even use injection in your unit tests, because they get resolved in the @BeforeClass method of the base class.

    private @Inject CustomerService custSvc;
    private @Inject UserService usrSvc;
...

And just use the injected resources in your test

    @Test(groups = "createData")
    public void testCustomerCreation() throws Exception {
        Customer cust1 = new Customer();
        ...
        custSvc.createCustomer(cust1);
    }

Btw, to use the latest Apache DeltaSpike snapshots you might need to configure the following maven repository in your project or ~/.m2/settings.xml :

  <repositories>
    <repository>
      <id>people.apache.snapshots</id>
      <url>https://repository.apache.org/content/repositories/snapshots/</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

Limits

The CdiContainer based unit tests work really fine if your project under test does not rely on EE container resources, like JNDI or JMS. It might work for e.g. EJB if you use Apache OpenWebBeans plus Apache OpenEJB or JBoss Weld plus JBoss microAS.
I would need to test this out to give more detailed infos.
As a general rule of thumbs: once you need heavyweight EE resources in your backend, you should go the Arquillian route.

If you only use JNDI for configuring a JPA DataSource then please look at the documentation of Apache MyFaces CODI ConfigurableDataSource. This will give you much more flexibility and removes the need of the almost-ever-broken JNDI configuration in your persistence.xml. Instead you can use plain CDI mechanics to configure your database settings.

Summary

If you like to do more end-to-end or integration testing, then Arquillian is worth looking at. If you just need a quick unit test for your CDI based project, then take a look at my CdiContainerTest base class and grab the ideas you like. And don’t forget to provide feedback 😉

Advertisements

About struberg
I'm an Apache Software Foundation member blogging about Java, µC, TheASF, OpenWebBeans, Maven, MyFaces, CODI, GIT, OpenJPA, TomEE, DeltaSpike, ...

6 Responses to Unit Testing Strategies for CDI based projects

  1. Have you checked out jeeunit? (http://code.google.com/p/jeeunit)

    It is ultra-lightweight compared to Arquillian, you don’t need a @Deployment and you don’t even have to derive your test classes from a base class. All you need is a @RunWith(JeeunitRunner.class) annotation.

    jeeunit not only supports GlassFish, JBoss and Resin embedded servers, but also Java SE Weld and OpenWebBeans containers.

    If there’s anything missing to cover your use cases, I’m happy to add it. Besides, I’m in the process of merging jeeunit into Pax Exam 3.x to create a grand unified in-container testing framework for OSGi, Java EE, CDI and all the rest 🙂

  2. struberg says:

    Hi!

    I’ll try to give it a ‘test run’ on the weekend.

    LieGrue,
    strub

  3. Needle is also a lightweight alternative for testing java ee components outside of the container in isolation.

    http://needle.spree.de/

  4. Bryn Cooke says:

    Just to add to the mix, CDI-unit is a light weight junit runner for testing CDI applications.

    http://jglue.org/cdi-unit/

  5. Pingback: Unit Testing JEE Applications with CDI - NovaTec Blog

  6. Thomas Scheibelreiter says:

    Hi struberg

    I’m currently writing my bachelor thesis and would like to cite your article.
    Also if you happen to know other good sources comparing integration test frameworks, I’d love to hear from you.

    Best Regards

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: