Integration Testing for Java EE

By Adam Bien
Published September 2011

Pragmatic integration testing can increase your productivity and ensure the deployability of your Java EE 6 application.

Downloads:

  • Java EE
  • Introduction

    In my previous article , “Unit Testing for Java EE”, I covered unit testing of Java Platform, Enterprise Edition 6 (Java EE 6) applications by mocking out all external dependencies with Mockito. Unit tests are important for the validation of application business logic, but they do not ensure the deployability of your Java EE 6 application.

    Note: On Java.Net, you will find a Maven 3 project for this article, TestingEJBAndCDI, which was tested with NetBeans 7 and GlassFish v3.x.

    Separation of (Performance) Concerns

    Unit tests are fast and fine-grained. Integration tests are slow and coarse-grained. Instead of using arbitrary categorizations for unit and integration tests, such as “slow” or “fast,” to improve productivity, we could rely on their natural properties. Unit tests are finer-grained, so they should run first. You are usually writing small chunks of functionality, before the functionality is integrated into a larger subsystem. Unit tests are extremely fast. Hundreds of unit tests can be executed in milliseconds. You can iterate faster with unit tests, without waiting for the completion of integration tests.

    Integration tests are performed after a successful execution of unit tests. Integration tests are, therefore, executed less frequently, because unit tests will naturally fail often. With this strict separation between unit tests and integration tests, we can save several minutes (sometimes, hours) with each turnaround.

    Testing for Productivity

    Pragmatic integration testing does increase your productivity. The low-hanging fruit is testing of Java Persistence API (JPA) mapping and queries. Deploying the whole application to the server just to test the syntactical correctness of mapping and queries takes too long.

    Fortunately JPA persistence can be started directly in the unit test. The bootstrapping overhead is negligible. You only have to create the EntityManager with the EntityManagerFactory. For a mapping test of the Prediction entity, a real EntityManager instance is injected into the PredictionAudit class (see Listing 1).

    
    
        public class PredictionAuditIT {
        private PredictionAudit cut;
        private EntityTransaction transaction;
     
     @Before
     public void initializeDependencies(){
        cut = new PredictionAudit();
     cut.em = Persistence.createEntityManagerFactory("integration").createEntityManager();
        this.transaction = cut.em.getTransaction();
     }
     @Test
     public void savingSuccessfulPrediction(){
        final Result expectedResult = Result.BRIGHT;
        Prediction expected = new Prediction(expectedResult, true);
        transaction.begin();
        this.cut.onSuccessfulPrediction(expectedResult);
        transaction.commit();
        List<Prediction> allPredictions = this.cut.allPredictions();
        assertNotNull(allPredictions);
      
        assertThat(allPredictions.size(),is(1));
     }
     @Test
     public void savingRolledBackPrediction(){
        final Result expectedResult = Result.BRIGHT;
        Prediction expected = new Prediction(expectedResult, false);
        this.cut.onFailedPrediction(expectedResult);
     }
    }
    
    

    Listing 1: Injection of Unmanaged EntityManager

    Since the EntityManager runs outside the container, the transactions can be managed only by the unit tests. Declarative transactions are not available in this case. This further simplifies the testing, because the transaction boundary can be explicitly set inside a test method. You can easily flush the EntityManager cache with an explicit EntityTransaction#commit() call. Right after the flush, the data is available in the database and can be validated for test purposes (see method savingSuccessfulPrediction in Listing 1).

    Standalone JPA Configuration

    EntityManager is part of the JPA specification and was already included with the glassfish-embedded-all dependency. Fortunately, the same dependency comes with the EclipseLink implementation. You need only an external database to persist the data. Derby database doesn’t require any installation and can be used in the server, embedded, and in an in-memory mode.

    
    
    
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.7.1.1</version>
            <scope>test</scope>
        </dependency>
    
    
    

    Listing 2: “Installing” the Derby Database

    Derby is maintained in the standard Maven repository and can be included with a single dependency (see Listing 2). Test-scoped dependency is used, because the Java DataBase Connectivity (JDBC) driver is needed only at test execution time, and it doesn’t have to be deployed or installed on the server.

    Because we are going to start the unit test outside the container, we cannot rely on the availability of Java Transaction API (JTA) transactions and javax.sql.DataSource.

    
    
    
        <persistence version=“1.0” xmlns="#" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                xsi:schemaLocation="# 
                                    ">http://bit.ly/cDXfCA
            <persistence-unit name="integration" transaction-type="RESOURCE_LOCAL">
                <class>com.abien.testing.oracle.entity.Prediction</class>
                <exclude-unlisted-classes>true</exclude-unlisted-classes>
                <properties>
                    <property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:testDB;create=true"/>
                   <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
                    <property name="eclipselink.ddl-generation" value="create-tables"/>
                </properties>
            </persistence-unit>
        </persistence>
    
    

    Listing 3: Unit Test-Specific persistence.xml Configuration

    An additional, dedicated persistence.xml is created in the src/test/java/META-INF package and is used for testing exclusively. Because there is no deployment process, all entities have to be listed explicitly. Also transaction-type is set to RESOURCE_LOCAL, which enables manual transaction handling. Instead of a datasource declaration, the EntityManager talks directly to the database via the configured JDBC driver. The embedded Derby database configuration is the most convenient for unit testing. The EmbeddedDriver supports two connection strings: file persistence and in-memory persistence. For JPA mapping and query testing, the in-memory connection string (see Listing 3) is the way to go. All tables are created on-the-fly in memory before each test, and they vanish after the test execution. Because you don’t have to clean up your data after the test, this is the most convenient setup for JPA smoke tests.

    More-complex JPA tests require a defined set of test data, which cannot be met with an in-memory setup. Derby database can also use files instead of memory to persist and load its data. You only have to modify the connection string for this purpose:

    <property name="javax.persistence.jdbc.url" value="jdbc:derby:./sample;create=true”/>

    In particular, tests requiring a predefined set of data can be conveniently performed with the file-persistence configuration. The populated database has to be copied into the project folder before the test execution and it has to be deleted afterwards. Hence, the database is deleted after each run. You don’t even have to worry about cleanup or any modifications.

    Unit Test Is Not Integration Test

    You probably already noticed the strange looking “IT” suffix in the PredictionAuditIT class. The suffix is used to separate unit tests from the integration tests. The standard Maven failsafe plug-in executes all classes ending with IT or ITCase or starting with IT, but these classes are ignored by the Maven Surefire plug-in and the JUnit tests. You only have to add the following dependency to separate integration tests from unit tests:

    
    
     <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>2.7.1</version>
     </plugin> 
           
           

    Listing 4: Failsafe Plug-in Configuration

    Unit tests are executed during the standard mvn clean install execution, and they can also be explicitly started with mvn surefire:test. Integration tests can also be included in the Maven phases with the execution tag:

    
    
    
        <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.7.1</version>
        <executions>
              <execution>
                  <id>integration-test</id>
                  <phase>integration-test</phase>
                  <goals>
                      <goal>integration-test</goal>
                  </goals>
              </execution>
              <execution>
                  <id>verify</id>
                  <phase>verify</phase>
                  <goals>
                      <goal>verify</goal>
                  </goals>
              </execution>
        </executions> 
      </plugin>
    
    
    

    Listing 5: Phase Registration of Failsafe Plug-in

    With the execution tag, the Failsafe plug-in gets automatically executed with the mvn install, mvn verify, or mvn integration-test commands. Unit tests are executed first, and then the integration tests are executed.

    A strict separation between integration tests and unit tests significantly speeds up the turnaround cycle. Hence, unit tests validate the functionality of a method with a mocked-out environment, and they are orders of magnitude faster than integration tests. You get instantaneous feedback after the execution of unit tests.

    Only a successful execution of all unit tests triggers the naturally slower integration tests. Depending on the environment, you can also set up dedicated Continuous Integration jobs for unit tests and integration tests. The mvn clean install job triggers the mvn integration-tests job then. The job separation gives you even more flexibility; you can re-execute the jobs separately and get notifications about the progress of each job.

    A Killer Use Case for Embedded Integration Tests

    The vast majority of all tests can be performed without firing up the container. JPA persistence can be easily tested with a locally created EntityManager. Also, the business logic can be tested outside the container in a convenient and effective way. Usually, you will even mock out all dependent classes, services, and other plumbing.

    Let’s assume we have to externalize two error messages and keep them in one place for easy maintenance. String instances are injectable and can be used for configuration purposes (see Listing 6).

    
    
        @Inject
        String javaIsDeadError;
        @Inject
        String noConsultantError;
              //…
            if(JAVA_IS_DEAD.equals(prediction)){
                throw new IllegalStateException(this.javaIsDeadError);
            }
              //…
            if(company.isUnsatisfied()){
                throw new IllegalStateException(this.noConsultantError);
    
    
    

    Listing 6: Externalization with String Injection

    The class MessageProvider maintains the configuration and produces field-dependent values (see Listing 7).

    
    
        @Singleton
        public class MessageProvider {
                public static final String NO_CONSULTANT_ERROR = "No consultant to ask!";
                public static final String JAVA_IS_DEAD_MESSAGE = "Please perform a sanity / reality check";
        
            private Map<String,String> defaults;
       
            @PostConstruct
            public void populateDefaults(){
                this.defaults = new HashMap<String, String>(){{
                    put("javaIsDeadError", JAVA_IS_DEAD_MESSAGE);
                    put("noConsultantError", NO_CONSULTANT_ERROR);
                }};
            }
        
            @Produces
            public String getString(InjectionPoint ip){
                String key = ip.getMember().getName();
                return defaults.get(key);
            }
        }
    
    

    Listing 7: A Generic Configurator

    If you look at MessageProvider (Listing 7), there is nothing left to unit test. You could mock out the InjectionPoint parameter or the field Map defaults to test the lookup. The only critical parts are the mechanics behind the dependency injection itself in the getString method. The extraction of the field name, the lookup, and the injection itself can be reasonably tested only with the injection target inside the container. The injection target is the class OracleResource, which uses the injected values for exception creation. (See my previous article, “Unit Testing for Java EE.") You will have either to expose the injected values or extract the message from the exception to test the mechanism. You would not be able to test corner cases, for example, unconfigured fields, with this approach. A helper class built specifically for test purposes gives you more flexibility and simplifies the test significantly.

    
    
    
    public class Configurable {
        @Inject
        private String shouldNotExist;
        @Inject
        private String javaIsDeadError;
     
        public String getShouldNotExist() {
            return shouldNotExist;
        }
     
        public String getJavaIsDeadError() {
            return javaIsDeadError;
        }
    }
    
    

    Listing 8: Integration Test Helper Class

    The class Configurable (Listing 8) resides in the folder src/test/java and was specifically designed to simplify the integration tests for the MessageProvider class. The javaIsDeadError field is supposed to be injected, and shouldNotExist was not meant to be configured and should have the value null after the test.

    Aliens May Help You

    Arquillian is an interesting alien and an open source testing framework integrated as a JUnit TestRunner. You have a full control over which classes are deployed and injected. Arquillian executes JUnit tests and has full access to the contents of the src/test/java folder. Dedicated test-helper classes such as Configurable (in Listing 8) can be used to simplify testing without packaging them with the production code in src/main/java.

    Arquillian consists of a runner and container part. The arquillian-junit runner executes tests and realizes dependency injection inside a test case.

    
    
    <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-junit</artifactId>
            <version>1.0.0.Alpha5</version>
            <scope>test</scope>
     </dependency>
     <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
            <version>1.0.0.Alpha5</version>
            <scope>test</scope>
     </dependency> 
    
    

    Listing 9: Arquillian Maven 3 Configuration

    The arquillian-glassfish-embedded-3.1 dependency integrates an application server implementation. You can use arquillian-glassfish-embedded-3.1 (also 3.0), arquillian-jbossas-embedded-6 (and older versions), Tomcat, Weld, or Jetty (see Listing 9).

    
    
     import org.jboss.shrinkwrap.api.*;
     import javax.inject.Inject;
     import org.jboss.arquillian.junit.Arquillian;
     import org.junit.*;
     import static org.junit.Assert.*;
     import static org.hamcrest.CoreMatchers.*;
      
     @RunWith(Arquillian.class)
     public class MessageProviderIT {
        @Inject
        MessageProvider messageProvider;
        @Inject
        Configurable configurable;
      
        @Deployment
        public static JavaArchive createArchiveAndDeploy() {
            return ShrinkWrap.create(JavaArchive.class, "configuration.jar").
                addClasses(MessageProvider.class, Configurable.class).
                addAsManifestResource(
                new ByteArrayAsset("<beans/>".getBytes()),
                ArchivePaths.create("beans.xml"));
     }
      
     @Test
     public void injectionWithExistingConfiguration() {
        String expected = MessageProvider.JAVA_IS_DEAD_MESSAGE;
        String actual = configurable.getJavaIsDeadError();
        assertNotNull(actual);
        assertThat(actual,is(expected));
     }
     
     @Test
     public void injectionWithMissingConfiguration(){
        String shouldNotExist = configurable.getShouldNotExist();
        assertNull(shouldNotExist);
     }
    
    

    Listing 10: Embedded Integration Tests with Arquillian

    After the configuration of dependencies, you can use Arquillian as the test runner. The unit tests are executed by Arquillian transparently. Maven, Ant, or even your IDE will just use Arquillian instead of the original test runner to execute the tests. Although this indirection is transparent to you, it allows Arquillian to inject the deployed Contexts and Dependency Injection (CDI) or Enterprise JavaBeans (EJB) components or other Java EE resources directly to your tests. Arquillian is only a thin layer above the application server implementation. It boots GlassFish, JBoss, or Tomcat behind the scenes and executes the tests on a “native” embedded application server. It uses, for example, Embedded GlassFish behind the scenes.

    Arquillian requires you to deploy the archive first. In the method createArchiveAndDeploy from Listing 10, a JAR archive with the classes MessageProvider and Configurable and the empty beans.xml is created and deployed. The deployed classes can be directly injected to the unit test itself, which greatly simplifies the testing. The method injectionWithExistingConfiguration asks the test helper Configurable to return the javaIsDeadError and shouldNotExist values (see Listing 8). You can test your code as if your test would be executed inside the application server. It’s only an illusion and the opposite is true: The test starts the container.

    Testing the Impossible

    It is hard to predict what happens if the dependency Instance<Consultant> company is unsatisfied. It actually cannot happen in production, because always enough Consultant implementations are deployed. This makes this corner case untestable in an integration environment. With Arquillian, it is very easy to test unsatisfied and ambiguous dependencies by manipulating the deployment unit.

    
    
    @RunWith(Arquillian.class)
    public class OracleResourceIT {
     
        @Inject
        OracleResource cut;
     
        @Deployment
        public static JavaArchive createArchiveAndDeploy() {
            return ShrinkWrap.create(JavaArchive.class, "oracle.jar").
                addClasses(OracleResource.class,MessageProvider.class, Consultant.class).
                addAsManifestResource(
                new ByteArrayAsset("<beans/>".getBytes()),
                ArchivePaths.create("beans.xml"));
        }
     
        @Test(expected=IllegalStateException.class)
        public void predictFutureWithoutConsultants() throws Exception{
            try {
                cut.predictFutureOfJava();
            } catch (EJBException e) {
                throw e.getCausedByException();
            }
        }
    }
        
    

    Listing 11: Testing the Impossible

    Only the absolutely necessary classes, OracleResource, MessageProvider, and Consultant, are deployed with the oracle.jar archive created in the method createArchiveAndDeploy in Listing 11. Although the OracleResource class also uses an Event to send the prediction Result (see Listing 12), we are ignoring the PredictionAudit event listener by not deploying it.

    
    
    @Path("javafuture")
    @Stateless
    public class OracleResource {
     
        @Inject
        Instance<Consultant> company;
    
      @Inject
    Event<Result> eventListener;
     
        @Inject
        private String javaIsDeadError;
        @Inject
        private String noConsultantError;
    //…business logic omitted
    }
    
    

    Listing 12: Required Dependencies of OracleResource

    The Event is expected to be swallowed. The method predictFutureWithoutConsultants invokes the method OracleResource#predictFutureOfJava and expects an IllegalStateException caused in the precondition check checkConsultantAvailability (see Listing 13).

    
    
        public String predictFutureOfJava(){
               checkConsultantAvailability();
            Consultant consultant = getConsultant();
            Result prediction = consultant.predictFutureOfJava();
            eventListener.fire(prediction);
            if(JAVA_IS_DEAD.equals(prediction)){
                throw new IllegalStateException(this.javaIsDeadError);
            }
            return prediction.name();
        }
        
        void checkConsultantAvailability(){
            if(company.isUnsatisfied()){
                throw new IllegalStateException(this.noConsultantError);
          }
        }
    
    
    

    Listing 13: Precondition Check in OracleResource

    Interestingly, javax.ejb.EJBException is thrown instead of the expected IllegalStateException (see Listing 14).

    
    
    
    WARNING: A system exception occurred during an invocation on EJB OracleResource method 
    public java.lang.String com.abien.testing.oracle.boundary.OracleResource.predictFutureOfJava()
    javax.ejb.EJBException
    //omitting several lines of stacktrace…
        at $Proxy126.predictFutureOfJava(Unknown Source)
    
        at com.abien.testing.oracle.boundary.__EJB31_Generated__OracleResource__                    __Intf____Bean__.predictFutureOfJava(Unknown Source)
    
    

    Listing 14: Injection Stack Trace with an EJB Proxy

    The unit test accesses a real EJB 3.1 bean through its public interface. IllegalStateException is an unchecked exception, which causes the current transaction to roll back, and it is propagated as javax.ejb.EJBException. We have to unwrap the IllegalStateException from the EJBException and re-throw it during the test.

    Testing the Rollback

    The rollback behavior can also be easily tested with the introduction of another test helper: TransactionRollbackValidator. It is even an EJB 3.1 bean with access to the SessionContext.

    
    
    @Stateless
    public class TransactionRollbackValidator {
     
        @Resource
        SessionContext sc;
     
        @EJB 
        OracleResource os;
     
        public boolean isRollback(){
            try {
                os.predictFutureOfJava();
            } catch (Exception e) {
                //swallow all exceptions intentionally
            }
     
            return sc.getRollbackOnly();
        }
    }
    
    

    Listing 15: A Test Helper EJB 3.1 Bean

    The TransactionRollbackValidator test class invokes OracleResource, swallows all exceptions, and returns the current rollback status. We only have to slightly extend the deployment and test section of OracleResourceIT to test the rollback behavior as well (see Listing 16).

    
    
    @RunWith(Arquillian.class)
    public class OracleResourceIT {
    //other injections omitted
        @Inject
        TransactionRollbackValidator validator;
     
        @Deployment
        public static JavaArchive createArchiveAndDeploy() {
            return ShrinkWrap.create(JavaArchive.class, "oracle.jar").
                addClasses(TransactionRollbackValidator.class,
                    OracleResource.class,MessageProvider.class, Consultant.class).
                addAsManifestResource(
                new ByteArrayAsset("<beans/>".getBytes()),
                ArchivePaths.create("beans.xml"));
        }
        @Test
        public void rollbackWithoutConsultants(){
            assertTrue(validator.isRollback());
        }
        //already discussed unit test omitted
        }
    
    

    Listing 16: Testing the Rollback Behavior

    TransactionRollbackValidator is an EJB 3.1 bean, so it is deployed with the Required transaction attribute as default. With this convention, the method TransactionRollbackValidator#isRollback is always executed in a transaction scope. Either a new transaction is started or the existing one is reused. In our test case, the TransactionRollbackValidator invokes in a new transaction the OracleResource EJB 3.1 bean. The freshly started transaction is propagated to OracleResource, which throws the EJBException caused by the lack of consultants. TransactionRollbackValidator just returns the result of the SessionContext#getRollbackOnly invocation (see Listing 15).

    Such a test would be not possible without modification of the production code or without introduction of additional helper classes into the src/main/java source folder. Frameworks such as Arquillian give you a unique opportunity to easily test the infrastructure without polluting your production code with test helpers.

    …and Continuous Deployment?

    Testing your code outside and inside the container ensures only the correct behavior of your application under well-defined circumstances. Even a small server configuration change or a forgotten resource would break the deployment. All modern application servers are either scriptable or configurable through a headless API. Reconstructing the server from configuration maintained with your code not only minimizes potential errors, but also makes manual interactions superfluous. You could even bring your application into production on every push or commit. Solid integration and unit tests are the first prerequisite for “continuous deployment.”

    Overuse Hurts Productivity

    Testing everything inside an embedded container is convenient but unproductive. Java EE 6 components are annotated Plain Old Java Objects (POJOs) and can be easily unit tested and mocked out (see my previous article, “Unit Testing for Java EE.") with Java SE tools such as JUnit, TestNG, or Mockito. Using an embedded container to test your business logic is not only unproductive, but it is also conceptually wrong. Unit tests should validate your business logic, not the container behavior. Also, the majority of integration tests could be easily performed with a local EntityManager (see Listing 1) or a mocked-out container infrastructure. Only a fraction of all integration tests can be beneficially implemented with embeddable containers or test frameworks such as Arquillian. Although such tests represents only a fraction of the overall integration test base, these tests represent the ultimate killer use cases. Without an embeddable container, it is impossible to test infrastructure-dependent business logic without changing the integration environment or polluting the production code with test helpers.

    See Also

    About the Author

    Consultant and author Adam Bien is an Expert Group member for the Java EE 6 and 7, EJB 3.X, JAX-RS, CDI, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with servlets/EJB 1.0 in several large-scale projects, and he is now an architect and developer for Java SE and Java EE projects. He has edited several books about JavaFX, J2EE, and Java EE, and he is the author of Real World Java EE Patterns—Rethinking Best Practices and Real World Java EE Night Hacks—Dissecting the Business Tier. Adam is also a Java Champion and JavaOne 2009 Rock Star.