Now that the long awaited stable version of the Arquillian framework is released I wanted to demonstrate some interesting features of this framework that really eases writing and running of integration tests for Java EE 6 applications in many different ways.
In the following tutorial we are going to create some real-world examples using Enterprise JavaBeans, Contexts and Dependency Injection, the Java Persistence API and we’re finally running Drone/Selenium tests against a JEE Web Application that is using Java Server Faces.
Adding Arquillian to a Project
First of all we need the basic Arquillian framework so we’re adding the following snippet to our pom.xml
<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>
Second we need a Java EE 6 container to run the stuff on .. Arquillian allows you to easily switch between different implementations using Maven profiles.
I somehow like GlassFish and so I am adding the following dependency to my pom.xml – if you’d like to run your test against another container, please refer to the container adapter documentation.
In addition we need JUnit .. no surprise here …
<dependencies>
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1.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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
CDI Dependency Injection Testing
In our first scenario we want to test some good “old” dependency injection of an Enterprise Java Bean using CDI .. the first thing we need here is – of course – some CDI wired EJBs.
The stateless LocaleManager bean gets injected into the stateless TimeService bean
package com.hascode.tutorial.jee;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.ejb.Stateless;
@Stateless
public class LocaleManager {
public DateFormat getSpecialDateFormat() {
return new SimpleDateFormat("EEE, MMM d, yyyy");
}
}
package com.hascode.tutorial.jee;
import java.util.Date;
import javax.ejb.Stateless;
import javax.inject.Inject;
@Stateless
public class TimeService {
@Inject
private LocaleManager localeManager;
public String getLocalizedTime(final Date date) {
return localeManager.getSpecialDateFormat().format(date);
}
}
This is our final integration test .. first we do some bootstrap using ShrinkWrap, then we get the TimeService injected and we finally assure that all session beans we’re correctly wired.
package com.hascode.tutorial.jee;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Date;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(Arquillian.class)
public class TimeServiceIT {
private static final Date FIXED_DATE = new Date(1321009871);
@Inject
private TimeService timeService;
@Deployment
public static JavaArchive createArchiveAndDeploy() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(LocaleManager.class, TimeService.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
@Test
public void testCreateTimestamp() {
assertFalse("Sat, Feb 22, 2012".equals(timeService
.getLocalizedTime(FIXED_DATE)));
assertEquals("Fri, Jan 16, 1970",
timeService.getLocalizedTime(FIXED_DATE));
}
}
Running the test should produce the green bar that we developers love so much ;)
Java Persistence API Integration Tests
In the next example we’re going to test some EJBs making use of the Java Persistence API .. the first thing here is to define our persistence unit in src/main/resources/META-INF/persistence.xml.
Because we’re in a test environment here, we’re better off by adding another persistence.xml in src/test/resources/META-INF and use an HSQL database. I am using Toplink Essentials as persistence provider here because of the GlassFish-ized environment ;)
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="defaultPersistenceUnit" transaction-type="JTA">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>com.hascode.tutorial.jee.Book</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:testdb"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
As you can see, we’ve already referenced an entity there so this is our Book entity .. we’re using Bean Validation to add some constraints to our fields, too …
package com.hascode.tutorial.jee;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@NotNull
private String title;
@NotNull
private String author;
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(final String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(final String author) {
this.author = author;
}
@Override
public String toString() {
return "Book [id=" + id + ", title=" + title + ", author=" + author
+ "]";
}
}
In the next step we’re implementing a stateless session bean that holds a reference to the entity manager and allows us to save books and return a list of all available, persisted books
package com.hascode.tutorial.jee;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Stateless
public class BookEJB {
@PersistenceContext(unitName = "defaultPersistenceUnit")
private EntityManager em;
public Book saveBook(final Book book) {
em.persist(book);
return book;
}
public List<Book> findAllBooks() {
final Query query = em
.createQuery("SELECT b FROM Book b ORDER BY b.title ASC");
List<Book> entries = query.getResultList();
if (entries == null) {
entries = new ArrayList<Book>();
}
return entries;
}
public void deleteBook(final Book book) {
book = em.merge(book);
em.remove(book);
}
}
Finally the grand finale .. our integration test ..
package com.hascode.tutorial.jee;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(Arquillian.class)
public class BookServiceIT {
@Inject
private BookEJB bookEJB;
@Deployment
public static JavaArchive createArchiveAndDeploy() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(BookEJB.class, Book.class)
.addAsResource("META-INF/persistence.xml")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}
@Test
public void testFetchBooks() {
assertTrue(bookEJB.findAllBooks().isEmpty());
Book b1 = new Book();
b1.setAuthor("tim");
b1.setTitle("A fascinating book");
Book b2 = new Book();
b2.setAuthor("tom");
b2.setTitle("I R Coder");
Book b3 = new Book();
b3.setAuthor("maria");
b3.setTitle("Some book");
Book b4 = new Book();
b4.setAuthor("tim");
b4.setTitle("Another fascinating book");
bookEJB.saveBook(b1);
bookEJB.saveBook(b2);
bookEJB.saveBook(b3);
bookEJB.saveBook(b4);
assertEquals(4, bookEJB.findAllBooks().size());
bookEJB.deleteBook(b4);
assertEquals(3, bookEJB.findAllBooks().size());
}
}
Running the test you should see a shiny green bar again.
Testing a Java Server Faces Web App using Drone
First of all we need some additional dependencies in our pom.xml .. please add the following code
<dependencyManagement>
<dependencies>
[..]
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>1.0.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
[..]
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api-2.5</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Now that our pom.xml is stuffed we have to create a small Java Server Faces web application. Forgive me the code but we want to test against something “real” :)
In the first step add the following web.xml to src/main/webapp/WEB-INF .. we’re registering JSF and the welcome file here ..
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>hascode-arquillian</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/books.xhtml</welcome-file>
</welcome-file-list>
</web-app>
We’re too lazy to create too much additional classes here so we’re reusing the code from the JPA example. Anyhow we need a managed bean and this is it:
package com.hascode.tutorial.jee;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;
@ManagedBean(name = "bookControllerBean")
@RequestScoped
public class BookControllerBean {
@Inject
private BookEJB bookEJB;
private Book book = new Book();
public String save() {
bookEJB.saveBook(book);
return "/books.xhtml";
}
public List<Book> getBooks() {
return bookEJB.findAllBooks(); // we're lazy here ;)
}
public Book getBook() {
return book;
}
public void setBook(final Book book) {
this.book = book;
}
}
In the next step we’re adding the following facelet named books.xhtml in src/main/webapp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>
<ui:insert name="title">hasCode.com - Book Management App</ui:insert>
</title>
</h:head>
<h:body>
<p:panel>
<h:panelGrid columns="2" cellpadding="10">
<h:panelGroup>
<h1>
<ui:insert name="heading">Java EE 6 Tutorial - Blog Application</ui:insert>
</h1>
</h:panelGroup>
</h:panelGrid>
<h2>#{bookControllerBean.books.size()} Books available:</h2>
<ul>
<ui:repeat value="#{bookControllerBean.books}" var="book">
<li>
<h:outputText value="#{book.title} by #{book.author}">
</h:outputText>
</li>
</ui:repeat>
</ul>
<h2>Create new book:</h2>
<h:messages>
</h:messages>
<h:form id="createBookForm">
<h:panelGrid columns="3" cellpadding="10">
<label>Title</label>
<h:inputText label="Title" id="title" value="#{bookControllerBean.book.title}" required="true">
</h:inputText>
<h:message for="title">
</h:message>
<label>Author</label>
<h:inputText label="Author" id="author" value="#{bookControllerBean.book.author}" required="true">
</h:inputText>
<h:message for="author">
</h:message>
<h:commandButton title="Save" value="Save" id="saveBook" action="#{bookControllerBean.save}">
</h:commandButton>
</h:panelGrid>
</h:form>
</p:panel>
</h:body>
</html>
Finally our Drone test .. we get a hold of the Selenium instance using the @Drone annotation and we get the base URL using @ArquillianResource
package com.hascode.tutorial.jee;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URL;
import org.jboss.arquillian.container.test.api.Deployment;
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.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.thoughtworks.selenium.DefaultSelenium;
@RunWith(Arquillian.class)
public class CreateBookSeleniumTest {
private static final String WEBAPP_SRC = "src/main/webapp";
@Deployment(testable = false)
public static WebArchive createDeployment() {
return ShrinkWrap
.create(WebArchive.class, "books.war")
.addClasses(Book.class, BookEJB.class, BookControllerBean.class)
.addAsResource("META-INF/persistence.xml")
.addAsWebResource(new File(WEBAPP_SRC, "books.xhtml"))
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "faces-config.xml")
.setWebXML(new File("src/main/webapp/WEB-INF/web.xml"));
}
@Drone
DefaultSelenium browser;
@ArquillianResource
URL deploymentURL;
@Test
public void should_create_and_display_book() {
// this will be http://localhost:8181/books/books.xhtml
browser.open(deploymentURL + "books.xhtml");
// create a screenshot for the tutorial ;)
// browser.captureScreenshot("/tmp/screen.png");
browser.type("id=createBookForm:title", "My book title");
browser.type("id=createBookForm:author", "The author");
browser.click("id=createBookForm:saveBook");
browser.waitForPageToLoad("20000");
assertTrue(
"Book title present",
browser.isElementPresent("xpath=//li[contains(text(), 'My book title by The author')]"));
}
}
The result of the test run should look like the following screencast of mine on YouTube (hopefully your environment is a bit faster ;)
If you need to set some breakpoints and do some step debugging that’s no problem either .. as to be seen in the following screencast on YouTube.
Tutorial Sources
I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:
git clone https://github.com/hascode/arquillian-tutorial.git
Troubleshooting
-
“java.lang.RuntimeException: Could not invoke deployment method: public static org.jboss.shrinkwrap.api.spec.WebArchive // Caused by: java.lang.IllegalArgumentException: web.xml not found in classloader sun.misc.Launcher” – My web.xml resides in src/main/webapp – one reason for this is, that I am using an archetype that creates this structure for me .. to make ShrinkWrap add the web.xml from that location not on the direct classpath you need to pass in a java.io.File object and the web.xml should be added then.
Resources
Tutorial Sources
-
2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).