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:
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | <? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < 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 > </ repository > < repository > < id >JBOSS_NEXUS</ id > </ repository > < repository > < 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | <? xml version = "1.0" encoding = "UTF-8" ?> < persistence version = "2.0" 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
01 02 03 04 05 06 07 08 09 10 11 12 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | <? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" < 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
01 02 03 04 05 06 07 08 09 10 11 12 13 | <? xml version = "1.0" encoding = "UTF-8" ?> 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <? xml version = "1.0" encoding = "UTF-8" ?> 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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 viaProducer
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
:
1 | 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.
Heya!
Nice article. Great job of explaining the moving parts.
Just one little note; there is no need to include the arquillian-bom in the pom.xml’s dependency section, it’s only required in the dependencyManagement section :)
-aslak-
It would be nice to post sources somewhere.