Arquillian Tutorial: Writing Java EE 6 Integration Tests and more..

April 26th, 2012 by

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 ;)

JUnit Runner goes green

JUnit Runner goes green

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

Arquillian Selenium Drone Screenshot

Arquillian Selenium Drone Screenshot

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 (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

Tutorial Sources

I have put the source from this tutorial on my Bitbucket repository – download it there or check it out using Mercurial:

hg clone https://bitbucket.org/hascode/arquillian-tutorial

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

Tags: , , , , , , , , , , , ,

5 Responses to “Arquillian Tutorial: Writing Java EE 6 Integration Tests and more..”

  1. Aslak Knutsen Says:

    Very nice write up!

    To your troubleshooting: Using addAs|setXX(String) in ShrinkWrap will look for a Resource in your ClassLoader. web.xml is located in src/main/webapp, which is not normally not a part of your classpath.

    note: Arquillian Drone 1.0.0.Final has been released as well :)

  2. micha kops Says:

    thanks for your remarks! article updated :)

  3. Jacko Says:

    Good stuff! Thanks.

  4. João Henrique Says:

    Hello , how are everyone.

    Then, I’d like know how activate the file arquillian.xml to read in my arquillian project.

    Anyone have any idea?

  5. beginner Says:

    Wonderful article and nice examples! Thanks!!!!

Leave a Reply

Please leave these two fields as-is:

Protected by Invisible Defender. Showed 403 to 81,009 bad guys.

Search
Categories