Testing OpenLiberty with Arquillian (Remote)

Having heard many great reviews, I thought I’ll give Open Liberty a try.

In this post, I shall discuss the following:

  • Setup of Open Liberty
  • Setup JDBC connection
  • Setup Arquillian
  • Testing REST endpoint

Installing Open Liberty

At the time of writing, I am using Open Liberty 18.0.0.1, and I am using Java SE 1.8.0_172 (P.S. Keen to move on to Java 9 and Java 10, but I thought better wait for LTS Java 11).

Installation is very easy. Let’s assume we are going to create a running server name test.

First, unpack your Open Liberty download. It will create a directory structure wlp.

Navigate to bin directory and run the following command:

./server create test

Now a server name test has been created. To start:

./server start test

with the argument test being the name of the server.

Navigate to http://localhost:9080/test to see the context root.

To stop,

./server stop test

– Configure server.xml

Once you have start test server, a directory will be created under the /usr/servers/test, and inside that directory there is file named server.xml. Let’s check it out.

<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>jsp-2.3</feature>
    </featureManager>

    <!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
    <httpEndpoint id="defaultHttpEndpoint"
                  httpPort="9080"
                  httpsPort="9443" />

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>
</server>

In this article we are using Java EE 7 Web Profile, to enable that, it is very simple to do that (and it is not even necessary to restart the server). Simply change the featureManager.

<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>webProfile-7.0</feature>
    </featureManager>

    <!-- the rest of the configuration omitted -->

You can check what features being loaded dynamically by looking at console.log.

Configuring JDBC Datasource

– Configure Datasource in server.xml

For this exercise, I am using MySQL 8.0. Setting up MySQL and its configuration is out-of-scope of this article.

Let’s assume we have created a new database, also named test.

To set up your datasource, make the following modification to your server.xml and restart (or not, not too sure on this one, but no harm in restarting).

Comments interleaved.

<?xml version="3.0" encoding="UTF-8"?>
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>webProfile-7.0</feature>
    </featureManager>

    <!-- Declare the jar files for MySQL access through JDBC. -->
    <dataSource id="testDS" jndiName="jdbc/testDS">
        <jdbcDriver libraryRef="MySQLLib"/>
        <properties databaseName="test"
                  serverName="localhost" portNumber="3306"
                  user="root" password="P4sswordGoesH3r3"/>
    </dataSource>
    <library id="MySQLLib">
        <file name="/home/dwuysan/dev/appservers/wlp/usr/shared/resources/mysql/mysql-connector-java-8.0.11.jar"/>
    </library>

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>
</server>

– persistence.xml

OpenLiberty comes with EclipseLink bundled as JPA Provider. In this example I have not configured any EclipseLink’s properties.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="testPU">
        <jta-data-source>jdbc/testDS</jta-data-source>        
        <properties>
        </properties>
    </persistence-unit>
</persistence>

And you can then call it in your Java EE application via:

@Stateless
@LocalBean
public class LogService {

    @PersistenceContext
    private EntityManager em;

    public Collection<Log> getLogs() {
        return this.em.createNamedQuery(Log.FIND_ALL, Log.class).getResultList();
    }
}

Setting up arquillian

In this article, we are going to implement Arquillian remote testing against a running OpenLiberty server.

Firstly, add arquillian to your pom.xml.

– Configure pom.xml

This is the pom.xml that have been modified:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>id.co.lucyana</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>test</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.4.0.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>        
        <dependency>
            <groupId>org.jboss.arquillian.graphene</groupId>
            <artifactId>graphene-webdriver</artifactId>
            <version>2.3.2</version>
            <type>pom</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <scope>test</scope>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>        
        <dependency>
            <!-- Arquillian WebSphere Liberty Profile support -->
            <groupId>io.openliberty.arquillian</groupId>
            <artifactId>arquillian-liberty-remote</artifactId>
            <version>1.0.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

– Make modification to server.xml

The documentation provided here is quite self-explanatory. Please consult this documentation for more up-to-date information on how to enable remote-testing.

<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>webProfile-7.0</feature>
        <feature>restConnector-2.0</feature>
    </featureManager>
    
    <!-- Declare the jar files for MySQL access through JDBC. -->
    <dataSource id="testDS" jndiName="jdbc/testDS">
        <jdbcDriver libraryRef="MySQLLib"/>
        <properties databaseName="test" 
                  serverName="localhost" portNumber="3306" 
                  user="root" password="P4sswordGoesH3r3"/>
    </dataSource>
    <library id="MySQLLib">
        <file name="/home/dwuysan/dev/appservers/wlp/usr/shared/resources/mysql/mysql-connector-java-8.0.11.jar"/>
    </library>

    <httpEndpoint httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint" host="*" />

    <!-- userName and password should also be set in arquillian.xml to these values -->
    <quickStartSecurity userName="admin" userPassword="admin" />

    <!-- Enable the keystore -->
    <keyStore id="defaultKeyStore" password="password" />

    <applicationMonitor updateTrigger="mbean" />
    <logging consoleLogLevel="INFO" />

    <!-- This section is needed to allow upload of files to the dropins directory, the remote container adapter relies on this configuration -->
    <remoteFileAccess>
        <writeDir>${server.config.dir}/dropins</writeDir>
    </remoteFileAccess>

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>
</server>

– Trust the server (i.e. certificate)

You need to have those keys trusted by your client as well, otherwise you’ll see SSL certificate trust errors, and you need to give permissions for the container adapter to write to the dropins directory” (Liberty-Arquillian 2018)

Once you have made all the necessary modification above, (and re-start the server), notice that there is a new directory created under <location of your OpenLiberty server>/usr/servers/test/resources/security with test being the name of the server we have initially created.

Notice there are two files being created, keys.jks and ltpa.keys. Now, we are interested in the keys.jks.

So that we can run our test from Netbeans (Maven), the JDK must trust the running OpenLiberty.

Check the certificate

keytool -list -v -keystore key.jks
Enter keystore password: 

The password here is basically what we created in our server.xml, particularly this line:

<!-- Enable the keystore -->
<keyStore id="defaultKeyStore" password="password" />

so, enter the password, and the output should be as follows:

*****************  WARNING WARNING WARNING  *****************
* The integrity of the information stored in your keystore  *
* has NOT been verified!  In order to verify its integrity, *
* you must provide your keystore password.                  *
*****************  WARNING WARNING WARNING  *****************

Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: default
Creation date: May 26, 2018
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=localhost, OU=test, O=ibm, C=us
Issuer: CN=localhost, OU=test, O=ibm, C=us
Serial number: 2a6c5b27
Valid from: Sat May 26 12:24:30 WITA 2018 until: Sun May 26 12:24:30 WITA 2019
Certificate fingerprints:
	 MD5:  63:92:B2:4A:25:E3:BB:3B:96:37:11:C1:A7:25:38:B5
	 SHA1: B6:38:95:88:FC:50:EC:A0:8E:41:4E:DE:B5:D4:8B:85:2E:61:A2:5F
	 SHA256: 9C:7B:6A:FA:46:8C:50:F2:7D:7B:C4:24:4B:15:78:5A:34:25:C8:43:D1:AB:4D:EE:C7:00:4C:AF:30:F5:5C:92
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions: 

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 88 F2 C2 32 73 73 B6 66   8F FA 42 85 1F 43 A5 AF  ...2ss.f..B..C..
0010: 84 33 62 D5                                        .3b.
]
]



*******************************************
*******************************************

Next, export the certificate

We now need to create a .cer. Use the following command:

keytool -export -alias default -file testwlp.crt -keystore key.jks
Enter keystore password:

Basically we are exporting the certificate of alias into a file named testwlp.crt. Now, a file named testwlp.crt should be created.

Lastly, let’s trust that certificate by importing that certificate into the JDK cacert

keytool -import -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit -alias testwlp -import -file testwlp.crt

P.S. Please note that, as pointed out by many experts (via Twitter), apparently there are many ways to ‘trust’ the server. I am sure there are better ways, and as much as possible I’d prefer not to touch any of the JDK’s files.

– Create arquillian.xml

Now all those plumbing works done, let’s add arquillian.xml accordingly.

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
    <engine>
        <property name="deploymentExportPath">target</property>
    </engine>
    <container qualifier="liberty-remote" default="true">
        <configuration>
            <property name="hostName">localhost</property>
            <property name="serverName">test</property>

            <!-- check the 'quickStartSecurity' on 'server.xml' -->
            <property name="username">admin</property>
            <property name="password">admin</property>

            <!-- check the 'server.xml' -->
            <property name="httpPort">9080</property>
            <property name="httpsPort">9443</property> 
        </configuration>
    </container>    
    <extension qualifier="webdriver">
        <!--<property name="browser">firefox</property>-->
        <property name="remoteReusable">false</property>
    </extension>
</arquillian>

Write a REST Test case

With all those setups done, you can now write an Arquillian Test case. Below is an example of a test case against a JAX-RS end point (Please excuse the simplicity of the test case, the point is to show how we can test using Arquillian-remote against OpenLiberty):

package id.co.lucyana.test.resource;

import id.co.lucyana.test.entity.Log;
import id.co.lucyana.test.services.LogService;
import id.co.lucyana.test.util.ApplicationConfig;
import java.net.URL;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;

@RunWith(Arquillian.class)
public class LogResourceTest {
    
    @Drone
    private WebDriver webDriver;

    @Deployment
    public static JavaArchive createTestArchive() {
        return ShrinkWrap.create(JavaArchive.class)
                .addClass(Log.class)
                .addClass(LogResource.class)
                .addClass(LogService.class)
                .addClass(ApplicationConfig.class)
                .addAsManifestResource("test-persistence.xml",
                        ArchivePaths.create("persistence.xml"));
    }

    @Test
    @RunAsClient
    public void testLogResource(@ArquillianResource URL url) {        
        this.webDriver.get(url.toString() + "resources/log");
        String pageSource = this.webDriver.getPageSource();
        System.out.println("RESULT: " + pageSource);
        Assert.assertTrue(true);
    }
}

References

DigiCert, 2018, ‘How to install the trusted root into Java cacerts Keystore’, DigiCert, accessed on 20 June 2018

Liberty-Arquillian, 2018, ‘Arquillian Liberty Remote Documentation’, GitHub. Inc, accessed on 20 June 2018

SSLShopper, 2008, ‘The Most Common Java Keytool Keystore Commands’, SSLShopper, accessed on 20 June 2018

Advertisements

Java EE integration testing with Arquillian (incl. JAX-RS, JPA, and JSR-352 Batch, Wildfly) using Chameleon, Shrinkwrap, Drone/Graphene

Continuing from my previous post here, I think I have learnt a few new (and exciting) things about Java EE and also Arquillian, and testing, and I would like to share it with you.

But before we start, I would like to first draw your attention to the following points (These are purely my opinion):

  • In anticipation with Oracle announcement stopping the commercial support of Commercial, I guess I decided to take a look at another leading Java EE application server, Wildfly, and I was so glad I did.
  • In the previous post I used embedded glassfish for testing. Whilst embedded Wildfly is also available, I personally find that testing using remote application server is so much “real”, or getting us closer to simulate the real production environment. Just as an example, if I want to test my application which is running on Wildfly 10 (which uses Java Mail, JMS), currently point to database 'xyz', all I need to do is to unzip the same version of application server used in real environment (simply rename it with -test) and point to another copy of 'xyz' database.

Anyway, let’s start.

In this post, I am going to cover the following:

  • The testing scenario (covering JAX-RS and JSR-352 Batch)
  • Very brief setup of Wildfly
  • Programming JAX-RS and JSR-352 Batch
  • Setting up Arquillian with Chameleon (incl. ShrinkWrap)
  • Setting up of Drone/Graphene

The scenario

The test is to invoke a RESTFUL web service running on remote Wildfly (which in turn will invoke a JSR-352 Batch). But, instead of testing it manually, i.e. opening a browser or using curl, we are going to code it as an Arquillian test.

So, that is quite straight-forward.

Installing Wildfly

At the time of writing, I am using Wildfly 10 CR4.

  • Just download it in wildfly.org, and unzip it.
  • First, you need to add user. so open a terminal and navigate to <unzip location>/bin/ and run ./bin/add-user.sh. Just follow the prompt.
  • set a JAVA_HOME
  • First, you need to add user. so open a terminal and navigate to <unzip location>/bin/ and run ./bin/add-user.sh. Just follow the prompt.
  • To run wildfly simply run ./bin/standalone.sh. However, please note by default Wildfly start with Java EE 7 Web Profile (JBoss 2014). I might want to use feature such as JMS, so to start Java EE 7 Full Profile, do ./bin/standalone.sh --server-config=standalone-full.xml

Develop a JSR-352 Batch

A JSR-352 Batch (referred as Batch in this article) programming model is quite extensive, but very straight-forward to understand and to use. But basically you can either following the read-process-write model, or roll-your-own batchlet model (Gupta 2013). But for more details, you can refer to the Oracle tutorial here. (Kannan 2013).

In this example, I am going to walk you through a very simple read-process-write Batch.

– Write a job XML

  • Firstly, create an empty beans.xml to enable CDI.
  • Secondly, you need to create a folder batch-jobs under the META-INF. If you use maven, then this goes under resources.
  • Under the newly created forder, add a Job XML. Please note that by convention, the name of the batch job is nothing but the job JSL XML file name, minus the .xml extension (Kannan 2013). So, let’s say we call it testJob.xml.
  • The testJob.xml Comments interleaved.

    <?xml version="1.0" encoding="UTF-8"?>
    <job id="testJob" 
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd"
         version="1.0">
        <step id="testStep">
            
            <!--
                Basically the way it works is this.
                
                A reader is executed to read an item, and then passed to the
                processor.
                
                Depending on the checkpoint-policy, a collection of processed
                items are then passed to the writer, to be written. And then
                transaction commit.
                
                Please note in this case since the checkpoint-policy is set to
                'item' and item-count="1", it means that each item processed is
                to be written and transactioni commit.
            -->
            <chunk checkpoint-policy="item" item-count="1">
                <reader ref="testBatchReader" />
                <processor ref="testBatchProcessor" />
                <writer ref="testBatchWriter" />
            </chunk>
        </step>
    </job>
    

– Write the Batch Reader, Processor and Writer

  • The TestBatchReader.java. Batch will stop processing when reader returns null. This is a very simple example that simply return an Integer up to 100. But it can be much sophisticated than this, such as reading a file, etc.

    package id.co.lucyana.hr.batch;
    
    import javax.batch.api.chunk.AbstractItemReader;
    import javax.inject.Named;
    
    @Named
    public final class TestBatchReader extends AbstractItemReader {
    
        private int counter = 0;
        private static final int MAX_COUNTER = 100;
    
        @Override
        public Object readItem() throws Exception {
            while (this.counter < MAX_COUNTER) {
                ++this.counter;
                return this.counter;
            }
            return null;
        }
    }
    
  • The TestBatchProcessor.java. Do nothing

    package id.co.lucyana.hr.batch;
    
    import javax.batch.api.chunk.ItemProcessor;
    import javax.inject.Named;
    
    @Named
    public final class TestBatchProcessor implements ItemProcessor {
    
        @Override
        public Object processItem(Object item) throws Exception {
            return item; // do nothing here
        }
    }
    
  • The TestBatchWriter.java. Again, this one can be much sophisticated, such as writing to the underlying datastore, etc.

    package id.co.lucyana.hr.batch;
    
    import java.util.List;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.batch.api.chunk.AbstractItemWriter;
    import javax.inject.Named;
    
    @Named
    public final class TestBatchWriter extends AbstractItemWriter {
    
        @Override
        public void writeItems(List items) throws Exception {
            Logger.getLogger(TestBatchWriter.class.getName()).log(Level.INFO, items.toString());
        }
    }
    

That’s is for your Batch programming. Now let’s start with JAX-RS to trigger Batch to run.

Develop an Restful web service

JAX-RS has been awhile, so I shall not bore you with the details. But basically all we want to achieve is to be able to invoke a Restful webservice, with a job name as a parameter to trigger a batch process.

This is the ApplicationPath

package id.co.lucyana.hr.util;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath(value = "resources")
public class ApplicationConfig extends Application {
}

And, this is the JAX-RS end point, receiving a job name as a parameter

package id.co.lucyana.hr.batch;

import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.batch.operations.JobSecurityException;
import javax.batch.operations.JobStartException;
import javax.batch.runtime.BatchRuntime;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Singleton
@LocalBean
@Path(value = "batch")
public class BatchManager {

    @Path(value = "start")
    @GET
    @Produces(value = "application/json")
    public long start(@QueryParam("job") String job) {
        Logger.getLogger(BatchManager.class.getName()).log(Level.INFO, 
                BatchRuntime.getJobOperator().getJobNames().toString());
        try {
            return BatchRuntime.getJobOperator().start(job, new Properties());
        } catch (JobStartException | JobSecurityException e) {
            Logger.getLogger(BatchManager.class.getName()).log(
                    Level.SEVERE, e.getMessage(), e);
            return -1l;
        }
    }
}

Okay, now take a deep breath… and LET’S TEST!!!

Set up Arquillian, Chameleon, Graphene, Drone, Selenium

Now after all of those development, it is time to test what we have written, and we are going to test it against the real running application server.

The first thing we need to do is to set up Arquillian/Chameleon

– Adding Arquillian/Chameleon

If you look at my previous post, referring to the pom.xml there, you may notice that there are a lot of information regarding to the container used scattered there, i.e. org.jboss.arquillian.container, etc. Chameleon hides this information and makes it so easy to moves between containers (Knutsen 2015).

So the changes on my pom.xml would be as follows.

<?xml version="1.0" encoding="UTF-8"?>
<!-- omitted -->

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.8.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.1.9.Final</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.arquillian.container</groupId>
            <artifactId>arquillian-container-chameleon</artifactId>
            <version>1.0.0.Alpha5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

<!-- omitted -->

And configure your arquillian.xml (which is located under your test/resources if you are using Maven) as follows:

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
    <defaultProtocol type="Servlet 3.0" />

    <container qualifier="wildfly" default="true">
        <configuration>
            <property name="chameleonTarget">wildfly:10.0.0.CR4:remote</property>
            <property name="username"><!-- your username goes here --></property>
            <property name="password"><!-- your password goes here --></property>
        </configuration>
    </container>
</arquillian>

– Adding Graphene/Drone/Selenium

Now, remember what we are trying to emulate is as if the user enter a URL to invoke JAX-RS web service, passing a job name to start a Batch job. BUT, we want to code it as a JUnit test. In order to do that, we are going to use the combination of Graphene, Drone and Selenium. You can read more details about them here. But basically these technologies are part of the Arquillian test platform catered for web UI testing.

First we need to modify our pom.xml and add the following:

<!-- omitted -->
    <dependencyManagement>
        <dependencies>
            <!-- omitted -->
            <dependency>
                <groupId>org.jboss.arquillian.selenium</groupId>
                <artifactId>selenium-bom</artifactId>
                <version>2.43.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>       
        <!-- Omitted -->
        <dependency>
            <groupId>org.jboss.arquillian.graphene</groupId>
            <artifactId>graphene-webdriver</artifactId>
            <version>2.0.3.Final</version>
            <type>pom</type>
            <scope>test</scope>
        </dependency>
<!-- omitted -->

And add snippet in our arquillian.xml.

<?xml version="1.0" encoding="UTF-8"?>
    <!-- omitted -->
    <extension qualifier="webdriver">
       <!--<property name="browser">firefox</property>-->
       <property name="remoteReusable">false</property>
   </extension>
</arquillian>

N.B. I could not get ‘firefox’ to work. So by default it would be the ‘htmlUnit’.

All good, the last part is to write the actual Arquillian test.

Write an Arquillian test case

First things first I need to share. And that is to package the application as an EAR. I have tried to deploy it as a (EJB) JAR, JAX-RS does not work, and then simply as WAR, Batch does not work. So, for the purpose of this testing, I deployed as EAR, and all seems to be happy (Nozaki 2015).

So, let’s code our Arquillian test. Comments interleaved.

package id.co.lucyana.hr.batch;

import id.co.lucyana.hr.util.ApplicationConfig;
import java.net.URL;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;

@RunWith(Arquillian.class)
public class BatchManagerTest {

    @Drone
    private WebDriver driver;

    @Deployment
    public static Archive<?> createTestArchive() {
        // just add classes required in your test
        final JavaArchive ejbJar = ShrinkWrap.create(JavaArchive.class, "ejb-jar.jar")
                .addClass(ApplicationConfig.class)
                .addClass(BatchManager.class)
                .addClass(TestBatchReader.class)
                .addClass(TestBatchProcessor.class)
                .addClass(TestBatchWriter.class)
                .addAsManifestResource("test-persistence.xml",
                        ArchivePaths.create("persistence.xml"))
                .addAsManifestResource("META-INF/beans.xml", 
                        ArchivePaths.create("beans.xml"))
                .addAsManifestResource("batch-jobs/testJob.xml")
                .addAsResource("ValidationMessages.properties");

        /*
         * Embedding war package which contains the test class is needed
         * So that Arquillian can invoke test class through its servlet
         * test runner
         */
        final WebArchive testWar = ShrinkWrap.create(
                WebArchive.class, "test.war").addClass(BatchManagerTest.class);
        
        final EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class)
                .setApplicationXML("test-application.xml")
                .addAsModule(ejbJar)
                .addAsModule(testWar);
        return ear;
    }

    @Before
    public void beforeEachTest() {
        this.driver.manage().deleteAllCookies();
    }
    
    /*
     * So that we do not hard-code the URL, simply use @ArquillianResource
     */
    @Test
    @RunAsClient
    public void testInvoke(@ArquillianResource URL url) {
        this.driver.get(url.toString() + "resources/batch/start?job=testJob");
        String pageSource = this.driver.getPageSource();
        System.out.println(pageSource);
        Assert.assertTrue(true);
    }
}

Please note the use of @ArquillianResource which allows us not to hard-code the application URL (Knutsen 2012).

Result

Yey!!! So, that’s it. Make sure you have your Wildfly up and running, you can now run your test. Shrinkwrap will package your EAR, then Arquillian/Chameleon will deploy it remotely to the running Wildfly server, then as client, Drone/Graphene/Selenium will invoke the URL to run a Batch called testJob.

Here is the snippet of the rest result:

18:07:00,585 INFO  [org.jboss.as.server] (management-handler-thread - 2) WFLYSRV0010: Deployed "a3ee3dad-d71c-41b0-9f57-9ae6e7ffe859.ear" (runtime-name : "a3ee3dad-d71c-41b0-9f57-9ae6e7ffe859.ear")
18:07:06,137 INFO  [id.co.lucyana.hr.batch.BatchManager] (default task-1) []
18:07:06,332 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [1]
18:07:06,333 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [2]
18:07:06,333 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [3]
18:07:06,334 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [4]
18:07:06,334 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [5]
18:07:06,334 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [6]
18:07:06,334 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [7]
18:07:06,335 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [8]
18:07:06,335 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [9]
18:07:06,335 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [10]
18:07:06,335 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [11]
18:07:06,336 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [12]
18:07:06,336 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [13]
18:07:06,336 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [14]
18:07:06,336 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [15]
18:07:06,337 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [16]
18:07:06,337 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [17]
18:07:06,337 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [18]
18:07:06,337 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [19]
18:07:06,338 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [20]
18:07:06,338 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [21]
18:07:06,338 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [22]
18:07:06,338 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [23]
18:07:06,339 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [24]
18:07:06,339 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [25]
18:07:06,339 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [26]
18:07:06,339 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [27]
18:07:06,340 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [28]
18:07:06,340 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [29]
18:07:06,340 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [30]
18:07:06,340 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [31]
18:07:06,341 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [32]
18:07:06,341 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [33]
18:07:06,341 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [34]
18:07:06,341 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [35]
18:07:06,341 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [36]
18:07:06,342 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [37]
18:07:06,342 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [38]
18:07:06,342 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [39]
18:07:06,343 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [40]
18:07:06,343 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [41]
18:07:06,343 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [42]
18:07:06,344 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [43]
18:07:06,344 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [44]
18:07:06,344 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [45]
18:07:06,345 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [46]
18:07:06,345 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [47]
18:07:06,345 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [48]
18:07:06,345 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [49]
18:07:06,346 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [50]
18:07:06,346 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [51]
18:07:06,346 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [52]
18:07:06,347 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [53]
18:07:06,347 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [54]
18:07:06,347 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [55]
18:07:06,347 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [56]
18:07:06,348 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [57]
18:07:06,348 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [58]
18:07:06,348 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [59]
18:07:06,348 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [60]
18:07:06,349 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [61]
18:07:06,349 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [62]
18:07:06,349 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [63]
18:07:06,349 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [64]
18:07:06,349 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [65]
18:07:06,350 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [66]
18:07:06,350 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [67]
18:07:06,350 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [68]
18:07:06,350 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [69]
18:07:06,350 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [70]
18:07:06,351 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [71]
18:07:06,351 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [72]
18:07:06,351 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [73]
18:07:06,351 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [74]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [75]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [76]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [77]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [78]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [79]
18:07:06,352 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [80]
18:07:06,353 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [81]
18:07:06,353 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [82]
18:07:06,353 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [83]
18:07:06,353 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [84]
18:07:06,353 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [85]
18:07:06,354 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [86]
18:07:06,354 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [87]
18:07:06,354 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [88]
18:07:06,354 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [89]
18:07:06,354 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [90]
18:07:06,355 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [91]
18:07:06,355 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [92]
18:07:06,355 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [93]
18:07:06,355 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [94]
18:07:06,356 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [95]
18:07:06,356 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [96]
18:07:06,356 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [97]
18:07:06,357 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [98]
18:07:06,357 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [99]
18:07:06,357 INFO  [id.co.lucyana.hr.batch.TestBatchWriter] (Batch Thread - 1) [100]
18:07:06,885 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 72) WFLYUT0022: Unregistered web context: /test

References

Allen et al., 2014, ‘Functional Testing using Drone and Graphene’, Red Hat Inc., accessed on 23 November 2015

Gupta, A, 2013, ‘Batch Applications in Java EE 7 – Undertanding JSR 352 Concepts: TOTD #192’, Oracle.com, accessed on 10 November 2015

JBoss, 2014, ‘Getting Started Guide – Wildfly 8’, JBoss.org, accessed on 10 November 2015

Kannan, M, 2013, ‘An Overview of Batch Processing in Java EE 7.0’, Oracle.com, accessed on 10 November 2015

Knutsen, A, 2012, ‘@ArquillianResource java.net.URL when test is run on the server’, JBoss Developer, accessed on 23 November 2015

Knutsen, A, 2015, ‘Arquillian Blog: Arquillian Container Chameleon 1.0.0.Alpha6 Released’, Red Hat Inc., accessed on 23 November 2015

Nozaki, K, 2015, ‘Arquillian EJB-JAR/EAR testing examples’, Kohei Nozaki’s blog, accessed on 23 November 2015

Oracle, 2013, ‘Java EE and GlassFish Server Roadmap Update’, Oracle.com, accessed on 10 November 2015

Testing Java EE 6 with Arquillian (incl. JPA, EJB, Bean Validation and CDI)

For a very long time, I heard quite a lot of people saying good things about Arquillian. Whilst I have been reading articles around its use, I couldn’t really find one that covers some of the aspects that I find important, all in a single article. Granted, I haven’t looked hard enough.

Points that I would like to cover are:

  • The use of JPA. I simply use EclipseLink here,
  • The use of in-memory database,
  • The use of CDI injection,
  • The use of EJB, say local Stateless session bean,
  • The use of JSR-303 Bean Validation,
  • The use of (embedded) glassfish for integration testing.

It took me a while to gather information to get such project up and running. I thought I dedicate this post to help out those who’s got similar requirement.

So, what are we waiting for!? Let’s start!!!

Configure pom.xml

Of course, we need to configure out project to use Arquillian, and also use EclipseLink as the persistence provider. But, bear in mind that we also decide before that we want to use an in-memory database. You might want to include your dependency here, in this pom.xml. I, however, decide to use Derby, which is available in the standard Oracle Java SDK classpath.

Anyway, so the pom.xml would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
		http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>inout</artifactId>
        <groupId>id.co.dwuysan</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>id.co.dwuysan</groupId>
    <artifactId>inout-ejb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>ejb</packaging>

    <name>inout-ejb</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <netbeans.hint.deploy.server>gfv3ee6</netbeans.hint.deploy.server>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.0.0.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>javax.persistence</artifactId>
            <version>2.0.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>

        <!-- test -->

        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.0.3.Final</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.glassfish.main.extras</groupId>
            <artifactId>glassfish-embedded-all</artifactId>
            <version>3.1.2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
            <version>1.0.0.CR3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- environment requirement -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <configuration>
                    <argLine>-XX:-UseSplitVerifier</argLine>
                    <systempropertyvariables>
                        <java.util.logging.config.file>
                        	${basedir}/src/test/resources/logging.properties
                        </java.util.logging.config.file>
                    </systempropertyvariables>
                    <systemProperties>
                        <property>
                            <name>derby.stream.error.file</name>
                            <value>target/derby.log</value>
                        </property>
                    </systemProperties>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <ejbVersion>3.1</ejbVersion>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>


                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>java.net</id>
            <url>http://download.java.net/maven/</url>
        </repository>
        <repository>
            <id>JBOSS_NEXUS</id>
            <url>http://repository.jboss.org/nexus/content/groups/public</url>
        </repository>
        <repository>
            <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
            <id>eclipselink</id>
            <layout>default</layout>
            <name>Repository for library EclipseLink (JPA 2.0)</name>
        </repository>
    </repositories>
</project>

As shown in the XML above, the things we have done are:

  • Include the use of Arquillian, using embedded Glassfish
  • Include the use of EclipseLink
  • Set Derby props for later use, i.e. logging and location of the database created.
    • Create out ‘case’, i.e. our JPA, EJB, CDI

      Of course, we first start by creating a case, so that we can then later test it. I assume you are familiar with JPA, EJB, CDI. Hence, following are very quick glimpses of classes using these technology.

      JPA class, Outlet.java

      package id.co.dwuysan.inout.entity;
      
      // imports omitted
      
      @Entity
      @NamedQueries({
          @NamedQuery(
              name = Outlet.FIND_BY_NAME,
              query = "SELECT o FROM Outlet o WHERE o.name = :name")
      })
      public class Outlet implements Serializable {
          
          public static final String FIND_BY_NAME = "Outlet#FIND_BY_NAME";
          
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          private Long id;
          
          @Column(name = "code", length = 50, insertable = true, 
                  updatable = false, unique = true)
          @Size(message = "{dwuysan.nameSizeError}", min = 1, max = 50)
          @NotNull
          private String name;
      
          /* Accessors and mutators goes here */
      
          @Override
          public int hashCode() {
              // omitted
          }
      
          @Override
          public boolean equals(Object obj) {
              // omitted
          }
      }
      

      Persistence.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <persistence version="2.0" 
      	xmlns="http://java.sun.com/xml/ns/persistence"
      	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence     http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
        <persistence-unit name="inoutPU" transaction-type="JTA">
          <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
          <jta-data-source>inoutDb</jta-data-source>
          <exclude-unlisted-classes>false</exclude-unlisted-classes>
          <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
          </properties>
        </persistence-unit>
      </persistence>
      

      Then let’s add a producer method to supply our PersistenceContext, as well as our EJB that uses it.

      EntityManagerProducer.java

      package id.co.dwuysan.inout.util;
      
      import javax.enterprise.inject.Produces;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      
      public class EntityManagerProducer {
      
          @Produces
          @PersistenceContext
          private EntityManager em;
      }
      

      OutletService.java

      package id.co.dwuysan.inout.service;
      
      // imports omitted
      
      @Stateless
      @LocalBean
      public class OutletService {
      
          @Inject
          private EntityManager em;
          
          @Resource
          private Validator validator;
      
          public Outlet createOutlet(final String name) {
              final Outlet outlet = new Outlet();
              outlet.setName(name);
              final Set<ConstraintViolation<Outlet>> violations = this.validator.validate(outlet);
              if (!violations.isEmpty()) { throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations)); }
              return this.em.merge(outlet);
          }
      
          public Outlet getOutlet(final String name) {
              final Query query = this.em.createNamedQuery(Outlet.FIND_BY_NAME);
              query.setParameter("name", name);
              try {
                  return (Outlet) query.getSingleResult();
              } catch (NoResultException e) {
                  return null;
              }
          }
      }
      

      Sets beans.xml and ValidationMessages.properties
      Don’t forget to:

      • add beans.xml under src/main/resources/META-INF, and
      • add ValidationMessages.properties under src/main/resources, and
      • configure your message dwuysan.nameSizeError=error message you like here

      Configure for testing purpose

      At this point, should you deploy, it should work. HOWEVER, that’s not our goal. We would like to get it working under Arquillian, using embedded Glassfish.

      Firstly, let’s prepare configuration for embedded glassfish, using Derby database. The file is glassfish-resources.xml. In my case, I simply put this file under a new directory, mainly for separation, i.e. src/test/resources-glassfish-embedded/glassfish-resources.xml.

      glassfish-resources.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE resources PUBLIC
          "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
          "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
      <resources>
          <jdbc-resource pool-name="ArquillianEmbeddedDerbyPool"
                         jndi-name="jdbc/arquillian"/>
          <jdbc-connection-pool name="ArquillianEmbeddedDerbyPool"
                                res-type="javax.sql.DataSource"
                                datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource"
                                is-isolation-level-guaranteed="false">
              <property name="databaseName" value="target/databases/derby"/>
              <property name="createDatabase" value="create"/>
          </jdbc-connection-pool>
      </resources>
      

      It is quite self-explanatory. Just remember to configure the database to be created on target/databases/derby so that when you do mvn clean it will be cleaned.

      Next step, is to configure Arquillian to “recognise” this glassfish-resources.xml. To do this, add arquillian.xml under the src/test/resources directory.

      glassfish-resources.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <arquillian xmlns="http://jboss.org/schema/arquillian"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
          <engine>
              <property name="deploymentExportPath">target/arquillian</property>
          </engine> 
          <container default="true" qualifier="glassfish">
              <configuration>
                  <property name="resourcesXml">src/test/resources-glassfish-embedded/glassfish-resources.xml</property>
              </configuration>
          </container>
      </arquillian>
      

      The next step is to prepare our persistence.xml. We already have one, but remember we need to supply the one which is in-memory, and make use of the jdbc connection provided by our embedded Glassfish (see glassfish-resources.xml above, which provide the jdbc-resource-pool under the JNDI name jdbc/arquillian. In my case, I named this test-persistence.xml, under src/test/resources

      test-persistence.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
          <persistence-unit name="inoutPU" transaction-type="JTA">
              <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
              <jta-data-source>jdbc/arquillian</jta-data-source>
              <exclude-unlisted-classes>false</exclude-unlisted-classes>
              <shared-cache-mode>ALL</shared-cache-mode>
              <properties>
                  <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
                  <property name="javax.persistence.jdbc.url" value="jdbc:derby:target/databases/derby;create=true" />
                  <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
                  <property name="eclipselink.target-database" value="Derby"/>
                  <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
                  <property name="eclipselink.debug" value="OFF"/>
                  <property name="eclipselink.weaving" value="static"/>
                  <!--<property name="eclipselink.logging.level" value="FINEST"/>-->
                  <property name="eclipselink.logging.level.sql" value="FINE"/>
                  <property name="eclipselink.logging.parameters" value="true"/>
                  <!--<property name="eclipselink.logging.level.cache" value="FINEST"/>-->
                  <property name="eclipselink.logging.logger" value="DefaultLogger"/>
              </properties>
          </persistence-unit>
      </persistence>
      

      When everything is ready, we are now ready to write our unit test, with Arquillian. In this case, it is best to test our service EJB, since that’s where we are going to use JPA, CDI, and the Validation.

      OutletServiceTest.java

      package id.co.dwuysan.inout.service;
      
      // imports omitted
      
      @RunWith(Arquillian.class)
      public class OutletServiceTest {    
          
          @Inject
          private OutletService outletService;
      
          @Deployment
          public static JavaArchive createTestArchive() {
              return ShrinkWrap.create(JavaArchive.class)
                      .addClass(Outlet.class)
                      .addClass(OutletService.class)
                      .addClass(EntityManagerProducer.class)
                      .addAsManifestResource("test-persistence.xml",
                      	ArchivePaths.create("persistence.xml"))
                      .addAsManifestResource("META-INF/beans.xml",
                      	ArchivePaths.create("beans.xml"))
                      .addAsResource("ValidationMessages.properties");
          }
          
          
          @Test
          public void testCreateOutlet() throws Exception {
              final String outletName = "Outlet 001";
              final Outlet outlet = this.outletService.createOutlet(outletName);
              Assert.assertNotNull(outlet);
      
              // check retrieval
              final Outlet retrievedOutlet = this.outletService.getOutlet(outletName);
              Assert.assertEquals(outlet.getName(), retrievedOutlet.getName());
          }
          
          @Test(expected = ConstraintViolationException.class)
          public void testCreateOutletWithEmptyName() throws Exception {
              try {
                  final Outlet outlet = this.outletService.createOutlet("");
              } catch (EJBException e) {             
                  final ConstraintViolationException cve = (ConstraintViolationException) e.getCause();
                  
                  Assert.assertEquals("Total error message should only be one",
                  	1, cve.getConstraintViolations().size());            
                  Assert.assertEquals("Message must be correct",
                  	"Name must be provided",
                  	cve.getConstraintViolations().iterator().next().getMessage());
                  throw cve;
              }
          }
      }
      

      In the above example, the first test is testing the successful case. Given a name, a retrieval should result in an Outlet entity return providing the same name as parameter. Underneath the surface though, if we look back at the body of the OutletService.java, we are actually testing:

      • Persistence (JPA), into the underlying Derby
      • EJB injected into this test/li>
      • PersistenceContext injected via Producer method (CDI)
      • Testing no validation violated
      • Testing our NamedQuery

      The second test is aimed to test that the message is interpolated correctly. Referring to what mentioned previously, for my error message, I have put the following entry in my ValidationMessages.properties:

      dwuysan.nameSizeError=Name must be provided
      

      So, we need to test that the message from Bean Validation in Outlet is interpolated correctly.

      Please pay attention to the second test. Notice that firstly, we are catching EJBException. That is because any runtime exception thrown inside an EJB will be wrapped into EJBException, hence the need to extract it via #getCause().

      So, there you go. You can now add more services and start your Arquillian test. Happy coding 🙂

      Future investigation

      Many Java EE application of course requires authentication and authorisation, which is generally done via JAAS. For example, using my simple example above, supposed that the service is to be modified to retrieve the outlets the current user has access to, then of course we need to get the current user’s identity. Generally, this is done via EJBContext.getCallerPrincipal(). I wonder how we can do this using Arquillian and embedded Glassfish.