Skip to main content

Embedded Integration Testing of Web Applications

April 12, 2007

{cs.r.title}



Do you speak test? In that case: hello, web application:

[prettify]public class WebIntegrationTest extends net.sourceforge.jwebunit.WebTestCase {

    public void testIndex() {
        beginAt("/index.html");
        assertTextPresent("Hello World!");
    }

    private org.mortbay.jetty.Server server;

    protected void setUp() throws Exception {
        // Port 0 means "assign arbitrarily port number"
        server = new org.mortbay.jetty.Server(0);
        server.addHandler(
                new org.mortbay.jetty.webapp.WebAppContext("src/main/webapp", "/my-context"));
        server.start();

        // getLocalPort returns the port that was actually assigned
        int actualPort = server.getConnectors()[0].getLocalPort();
        getTestContext().setBaseUrl("http://localhost:" + actualPort + "/my-context");
    }
}
[/prettify]

This code runs with no application server and no separate
deployment step, but it uses HTTP and a production web server. If
this looks interesting, keep reading, and I'll show you how it
works and a few more tricks, including how to use the same method
to unit test a web application constructed with Spring-MVC and
Hibernate.

The "Hello World" Embedded Integration Test

First: You can download the complete project to run the "hello
world" application from the Resources
section. To run the application, install "http://maven.apache.org">Maven 2 and run mvn test. To use
the code in an IDE, run mvn eclipse:eclipse or mvn idea:idea.
(Or see "http://maven.apache.org/guides/mini/guide-ide-netbeans/guide-ide-netbeans.html">
instructions for Netbeans
.) After you import the project into
your workspace, the test should run in memory, just as a normal
unit test.

If you want to follow the example by yourself, you can start out
by installing Maven
2
, and running the Maven Archetype command like so:

mvn
archetype:create -DgroupId=no.brodwall.insanejava.demo
-DartifactId=web-demo
-DarchetypeArtifactId=maven-archetype-webapp
. This will
create a skeletal Maven project.

Open the pom.xml file and add two more test dependencies:
groupId jwebunit, artifactId jwebunit, version 1.2 and

groupId
org.mortbay.jetty
, artifactId jetty, version 6.1.0. Create a
directory named src/test/java and place the
WebIntegrationTest shown above in it. For example, I
would place mine in
src/test/java/no/brodwall/web/integration/WebIntegrationTest.java.

If you have done everything right, your test will fail. This is
because we haven't added an index.html file. You can simply
rename the Maven-generated file src/main/webapp/index.jsp to
index.html. Run the tests again and they should succeed.
Congratulations, you have successfully run an embedded web
integration test
.

Spring Application Context

Now, let's try out a more sophisticated example: using
Spring-MVC and interacting with the Controller to
control the result. To do the rendering, I use "http://freemarker.sourceforge.net/">FreeMarker, a "template
engine," or in other words, "a generic tool to generate text output
based on templates." FreeMarker combines textual templates with
Java objects to create a view. Unlike JSPs, it does not
require a separate compile step. This means that it starts up
faster, which is good for unit tests. The use of FreeMarker
requires two new dependencies in the Maven 2 pom
(org.springframework:spring:2.0.1 and
freemarker:freemarker:2.3.8--and remember to
regenerate IDE files).

Okay, enough talk, here's another test for
WebIntegrationTest:

[prettify]public void testShowObject() {
    // Set up the objects that the controller depends on
    ModelObject pretendObject = new ModelObject();
    pretendObject.setAttributeOne("value one");
    pretendObject.setAttributeTwo(1234);

    // Set up the controller with the dummy ModelObject
    MyController controller = (MyController)getWebContextBean("controller");
    controller.setResult(pretendObject);

    // Execute the HTTP request
    beginAt("/my/controller.html");
    // Verify the result
    assertTextInElement("attributeOne", pretendObject.getAttributeOne());
    assertTextInElement("attributeTwo", Long.toString(pretendObject.getAttributeTwo()));    
}
[/prettify]

ModelObject and MyController are
classes in our application, so you should create them.
MyController will be the controller in this
Model-View-Controller application, which means that it will be the
class that handles the actual request. The ModelObject
will be a dependency it needs to do its job.

The test verifies that the values placed in the object that we
injected into the controller are displayed on the resulting web
page. Getting it to pass will require us to set up most of the
infrastructure for a Spring-MVC application. The first, and maybe
most interesting, question in your mind is probably: How do I get
the Spring bean from the Web Context?
Luckily, Spring provides
a way to do this, but you need to get to the right objects. Here is
the implementation. Take a deep breath:

[prettify]import org.mortbay.jetty.webapp.WebAppContext;
import javax.servlet.ServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class WebIntegrationTest extends WebtestCase {

    // ...

    private Object getWebContextBean(String beanName) {
        WebAppContext jettyWebAppContext = (WebAppContext) server.getHandler();
        ServletContext servletContext = jettyWebAppContext.getServletHandler().getServletContext();
        WebApplicationContext springWebAppContext =
            WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
        return springWebAppContext.getBean(beanName);
    }

}
[/prettify]

This test will currently fail. In order for Spring to
have an application context, we need to set up a few more
things: the src/main/webapp/web.xml must have a
ContextLoaderListener, and we need the
src/main/webapp/WEB-INF/applicationContext.xml. Personally,
I prefer adding the "http://www.springframework.org/docs/reference/webintegration.html">
ContextLoaderListener
and contextConfigLocation first, and then getting the
"FileNotFound" for the applicationContext.xml and adding that.
This helps me remember what to do next. Here is the
web.xml snippet:

[prettify]<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
[/prettify]

src/main/webapp/WEB-INF/applicationContext.xml will
in this case contain a single bean definition:
id="controller"
class="no.brodwall.insanejava.webintegration.MyController"
/>)
.

For the actual integration, we need to map the
/my path to Spring-MVC's DispatcherServlet and
/my/controller.html to the controller. This is done in
web.xml and the new my-servlet.xml. Add the "http://www.springframework.org/docs/reference/mvc.html#mvc-servlet">
org.springframework.web.servlet.DispatcherServlet
to
src/main/webapp/WEB-INF/web.xml:

[prettify]<servlet>
  <servlet-name>my</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>my</servlet-name>
  <url-pattern>/my/*</url-pattern>
</servlet-mapping>
[/prettify]

Running the tests again will give the following useful (really!)
message:

"Could not open ServletContext resource
[/WEB-INF/my-servlet.xml]"
. Create a new file,
src/main/webapp/WEB-INF/my-servlet.xml:

[prettify]<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="/controller.html">controller</prop>
      </props>
    </property>
  </bean>
</beans>
[/prettify]

After adding this code, running the test now gives me: "

No
adapter for handler [no.brodwall.web.MyController@80cac9]: Does
your handler implement a supported interface like
Controller?
." Which of course it doesn't. So we make
MyController implement
org.springframework.web.servlet.mvc.Controller, which
means implementing the handleRequest:

[prettify]public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
    return new ModelAndView("showResult", "result", this.result);
}
[/prettify]

Here, this.request was the value we set in the
test.

Now I get a "404 not found." This is because I have not told
Spring-MVC how to deal with the showResult view. I
have to inform Spring-MVC about my decision to use "http://freemarker.sourceforge.net/">FreeMarker. Here is the
"http://www.springframework.org/docs/reference/view.html#view-velocity-contextconfig">
Spring configuration of FreeMarker
in
my-servlet.xml:

[prettify]<bean id="viewResolver"
    class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="cache" value="true" />
    <property name="prefix" value="" />
    <property name="suffix" value=".ftl" />
</bean>

<bean id="freemarkerConfig"
      class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
      <property name="templateLoaderPath" value="/views" />
</bean>
[/prettify]

I can run the tests again. In that case, I get the sensible
message: "Template showResult.ftl not found." Place
this file in src/main/views/showResult.ftl:

[prettify]
<html>
<body>
  <p>Attribute One: <span id="attributeOne">${result.attributeOne}</span></p>
  <-- my only gripe with FreeMarker: By default, numbers are formatted fancy. string("#") overrides it -->
  <p>Attribute Two: <span id="attributeTwo">${result.attributeTwo?string("#")}</span></p>
</body>
</html>
[/prettify]

The test now passes. Here it is again:

[prettify]public void testShowObject() {
    // Set up the objects that the controller depends on
    ModelObject pretendObject = new ModelObject();
    pretendObject.setAttributeOne("value one");
    pretendObject.setAttributeTwo(1234);

    // Set up the controller with the dummy ModelObject
    MyController controller = (MyController)getWebContextBean("controller");
    controller.setResult(pretendObject);

    // Execute the HTTP request
    beginAt("/my/controller.html");
    // Verify the result
    assertTextInElement("attributeOne", pretendObject.getAttributeOne());
    assertTextInElement("attributeTwo", Long.toString(pretendObject.getAttributeTwo()));    
}
[/prettify]

What Just Happened?

In the beginning of the article, we added the "http://jwebunit.sourceforge.net/">JWebUnit HTTP-based test
library for web applications to the Maven project descriptor (POM).
JWebUnit has allowed us to write simple unit tests that verify
the output of the web pages using HTTP. It's very
important to notice that despite being reasonably fast (about two
seconds on my computer), these tests actually run with a real Java
servlet container.

Figure 1 shows the composition of our Spring-MVC application
stack.

<br "Simple example with Spring-MVC" width="450" height="315" />
Figure 1. The composition of a Spring-MVC application stack

The tests do the following:

  1. Start the Jetty application server with a web context defined
    by our web.xml file.
  2. The web.xml file sets up a Spring-MVC controller and
    maps it to a URL.
  3. The test gets the controller out of the Spring Web Application
    Context, and set its result object.
  4. The test uses the JWebUnit framework to accesses the controller
    via HTTP.
  5. The test verifies that the HTML returned over HTTP matched our
    expectations; that is, the attributes displayed in the web page
    were those we injected into the controller.

This example is pretty basic, but it serves as a base for more
advanced applications. Later, I will show how to put together a
complete CRUD (Create Retrieve Update Delete) application with
Hibernate, but first, I want so show how you can test this
application interactively.

Starting Jetty for Interactive Use

For minimal hassle, I usually create a class with a main method.
This is src/test/java/no/brodwall/web/WebServer.java:

[prettify]public class WebServer {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        server.addHandler(new WebAppContext("src/main/webapp", "/web-demo"));
        server.start();
    }
}
[/prettify]

Run the main class, and go to "http://localhost:8080/web-demo/my/controller.html">http://localhost:8080/web-demo/my/controller.html.
You will get a nice stack trace from FreeMarker. This is because
the result object returned by the controller is
null. Create a default object in
src/main/webapp/WEB-INF/applicationContext.xml to fix this
problem:

[prettify]<bean id="controller" class="no.brodwall.web.MyController">
  <property name="result">
    <bean class="no.brodwall.web.ModelObject">
      <property name="attributeOne" value="one" />
      <property name="attributeTwo" value="2" />
    </bean>
  </property>
</bean>
[/prettify]

You have to restart the application server before you can behold
the beauty, but when you do, you should be able to feast your eyes
on a very simple web page. Like JSPs, FreeMarker templates will be
reloaded at runtime if you edit them. Try and modify
src/main/webapp/views/showResult.ftl and hit the Refresh
button in your browser.

Alternatively, you can use the "http://jetty.mortbay.org/maven-plugin/howto.html">Maven 2 Jetty
plugin
, by adding the section to the
pom.xml file. I prefer having my own main method; that way,
debugging the application is much simpler.

Integration Testing a Simple CRUD Application

The introduction showed how to get started testing and
implementing a web application. I will now describe how to test a
full-fledged application. The application will allow for retrieving
and updating of a domain object, demonstrating the R and U of the
acronym CRUD (Create Retrieve Update Delete). The C (create) and
the D (delete) are left as exercises to the reader.

The article will not describe how to implement the code to make
the tests pass, but only the tests themselves. If you're curious about
the specifics, please see the companion source code (in the
Resources section).

Index: Show a List of Objects

The full application will be a CRUD application for a single domain class. I am using a class
named Category, as seen in Figure 2, as an example. Each
category has a name and a year it was created. Categories also have
subcategories, but we will ignore those for now. I will start the
testing effort using a "http://xunitpatterns.com/Test%20Double.html">test doubles for
the Data Access Object (DAO). This lets me focus on the web code
first. In order to make the tests pass, you should implement your
FakeCategoryDAO as simply as you can while getting the
tests to run.

Category domain model
Figure 2. The Category class

Here is a test showing how our web application shows a list of
categories. Put this test in a new
CategoryWebIntegrationTest class with the same setup of Jetty as
our simple test:

[prettify]public void testIndex() {
    // Create a Fake DAO and test data

    CategoryDAO categoryDao = new FakeCategoryDAO();
    Category firstCategory = new Category("first category");
    Category secondCategory = new Category("second category");
    categoryDao.insert(firstCategory);
    categoryDao.insert(secondCategory);

    // Inject the DAO it into the controller
    CategoryListController controller = (CategoryListController)getWebContextBean("listController");
    controller.setDao(categoryDao);

    // Go the the index page
    beginAt("category/index.html");
    // Verify that all data we expect is present
    assertTablePresent("categories");
    assertTextInTable("categories", firstCategory.getName());
    assertTextInTable("categories", secondCategory.getName());
}
[/prettify]

Like my simpler test, I manipulate the model objects behind the
controller, in this case the DAO that contains all our categories.
The test consists of four steps:

  1. Set up test data: Create the underlying data to be displayed
    and manipulated.
  2. Set up test objects: Replace the production DAO with the test
    double. We have to do this for all relevant controllers.
  3. Execute the code to be tested: In our case, this means
    executing a dialog towards a web page using JWebUnit.
  4. Check the result: In our case, this means checking the values
    displayed on a particular web page.

All tests of the CRUD web application follow the same pattern.
Again, see the companion source code on how to make the tests
pass.

testShowDetail

Let's zoom in on one of these categories:

[prettify]public void testDetail() {
    // Create a Fake DAO and test data
    CategoryDAO categoryDao= new FakeCategoryDAO();
    Category category = new Category("first category", 2007);
    categoryDao.insert(category);

    // Inject the DAO it into the controllers
    CategoryListController controller = (CategoryListController)getWebContextBean("listController");
    controller.setDao(categoryDao);
    CategoryController categoryController = (CategoryController)getWebContextBean("categoryController");
    categoryController.setDao(categoryDao);

    // Go to the list page and navigate to our object
    beginAt("category/index.html");
    clickLinkWithText(category.getName());

    // Verify that all attributes are displayed
    assertTextInElement("name", category.getName());
    assertTextInElement("creationYear", Integer.toString(category.getCreationYear()));
}
[/prettify]

I added a creationYear property to the Category
class, and a constructor that sets it up. I also created a new
controller: CategoryController. Just creating dummies
for these will make the code compile. Add
categoryController to
src/main/webapp/WEB-INF/applicationContext.xml to make it
available in the application context.

The interesting parts of the test are in the second half. We
expect the index page to have a link to our Category
object. And we expect the target of the link to display all
properties of the Category.

testEditName

If you follow along and try to write the code to make the tests
pass as well, you will have noticed that progress is probably slow
at this point. We are still creating the infrastructure, so this is
to be expected. Personally, I notice a markable speed-up at this
point. Let's try to edit the name of a category:

[prettify]public void testEditName() {
    // Refactor out the setup of the test data from previous tests
    populateTestdata();

    String newName = "new subcategroy 1 name";

    // Go to and verify the edit page
    beginAt("category/edit.html?id=" + category.getId());
    assertFormElementEquals("name", category.getName());
    // Update and submit the form
    setFormElement("name", newName);
    submit();

    // Check the update
    assertTextInElement("name", newName);
    // To avoid a temporary value from being displayed

    beginAt("category/show.html?id=" + category.getId());
    assertTextInElement("name", newName);
}
[/prettify]

Notice that I have refactored the setup of the test data into a
new method. I have updated the other tests correspondingly as
well.

We're Not Out of the Woods Yet

I have shown how you can test drive a simple CRUD web
application using JWebUnit and Jetty. But Spring-MVC and the
realities of Java web applications still have a few surprises in
stock for us. Taking the code all the way to production requires us
to persist the categories. I will implement a simple Hibernate DAO
for this.

The CategoryDAOTest

Our first order of business is to implement the CategoryDAO
interface using Hibernate. This interface has been evolving as we
have implemented new functional requirements. Parallel to these
changes, you should develop a test class for the CategoryDAO. The
new requirement is to make this test case work with the
HibernateCategoryDAO and to wire the application up to use the
HibernateCategoryDAO. This requires a new trick with Jetty: getting
the DataSource injected correctly.

Here is the current CategoryDAO interface we need to implement
our functional requirements. While creating the basic web tests, I
used a "fake" implementation of this that stores all the objects
in memory. This makes me able to create the tests without slowing
down to mess around with Hibernate.

[prettify]public interface CategoryDAO {

    void insert(Category category);

    Collection<Category> listAll();

    Category get(long id);

}
[/prettify]

I strongly recommend that you develop in-process integration
tests for the HibernateCategoryDAO. For more
information on this, see my java.net article on the subject,
" "http://today.java.net/pub/a/today/2005/10/11/testing-hibernate-mapping.html">
Unit Testing Hibernate Mapping Configurations
." An example of a
test method in CategoryDAOTest would be testing that
objects are persisted correctly:

[prettify]public void testFindAll() {
    Category fooCategory = new Category("foo");
    Category barCategory = new Category("bar");
    Category bazCategory = new Category("baz");
    categoryDAO.insert(fooCategory); categoryDAO.insert(barCategory); categoryDAO.insert(bazCategory);
    categoryDAO.flushAndEvict();

    Collection<Category> expected = Arrays.asList(fooCategory, barCategory, bazCategory);
    Collection<Category> actual = categoryDAO.listAll();
    // Use sets to make sure that ordering is ignored
    assertEquals(new HashSet<Category>(expected), new HashSet<Category>(actual));
}
[/prettify]

Or testing that unsaved changes are not returned:

[prettify]public void testUnsavedChanges() {
    Category category = new Category("foo");
    categoryDAO.insert(category);

    Category updatedCategory = categoryDAO.get(category.getId());
    updatedCategory.setName("bar");

    Category storedCategory = categoryDAO.get(updatedCategory.getId());
    assertEquals("foo", storedCategory.getName());
}
[/prettify]

This last test is interesting because it will fail! This may
lead to hard-to-find bugs in our code. My Hibernate testing article
(cited above) explains how to deal with this in detail, so I will
just explain it briefly here.

The problem is that the DAO returns the same object
instance. This is because our fake DAO just stores objects
in memory, but we will have the same problem with Hibernate. Like
most object-relation mapping frameworks, Hibernate uses the concept
of a session cache (also known as a first-level cache). The session
cache is made to ensure that we don't have multiple instances of
what is meant to be the same object lying around. To avoid hitting
this problem in our test, we can introduce the concept of
evicting objects from the cache. Here is the updated
test:

[prettify]public void testUnsavedChanges() {
    Category category = new Category("foo");
    categoryDAO.insert(category);
    categoryDAO.flushAndEvict();

    Category updatedCategory = categoryDAO.get(category.getId());
    categoryDAO.flushAndEvict();
    updatedCategory.setName("bar");

    Category storedCategory = categoryDAO.get(updatedCategory.getId());
    assertEquals("foo", storedCategory.getName());
}
[/prettify]

We will have to implement flushAndEvict for the
FakeCategoryDAO as well. We can do this in several
ways, but the fastest is to just clone all the objects. See the
companion source code for details.

With a decent CategoryDAOTest in place, we can now
create the Hibernate version of the test and of the DAO. I
recommend using Spring's Hibernate support, specifically
AnnotationSessionFactoryBean and
HibernateTemplate, for this. See the companion source
code if you're interested in the details.

HibernateCategoryDAOTest is a subclass of
CategoryDAOTest that sets the CategoryDAO
member variable of the superclass to
HibernateCategoryDAO. I have implemented this by
letting CategoryDAOTest initialize
categoryDAO with a call to the overrideable method
createCategoryDao:

[prettify]public class HibernateCategoryDAOTest extends CategoryDAOTest {

    protected CategoryDAO createCategoryDao() {
        return new HibernateCategoryDAO();
    }

}
[/prettify]

Running HibernateCategoryDAOTest will show fail
until we initialize the HibernateCategoryDAO correctly
with the HibernateTemplate. For now, let's do this in
createCategoryDao:

[prettify]protected CategoryDAO createCategoryDao() {
    SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
    dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
    dataSource.setUrl("jdbc:hsqldb:mem:test");
    dataSource.setUsername("sa");
    dataSource.setSuppressClose(true);

    Properties hibernateProperties = new Properties();
    hibernateProperties.setProperty(Environment.HBM2DDL_AUTO, "create-drop");

    AnnotationSessionFactoryBean sessionFactoryBean = new AnnotationSessionFactoryBean();
    sessionFactoryBean.setDataSource(dataSource);
    sessionFactoryBean.setHibernateProperties(hibernateProperties);
    sessionFactoryBean.setAnnotatedClasses(new Class[] { Category.class });

    try {
        sessionFactoryBean.afterPropertiesSet();
    } catch (Exception e) {
        throw new RuntimeException("SessionFactory misconfiguration", e);
    }
    HibernateTemplate hibernateTemplate = new HibernateTemplate((SessionFactory)sessionFactoryBean.getObject());

    return new HibernateCategoryDAO(hibernateTemplate);
}
[/prettify]

This code is long and annoying. The most important things to
notice are the facts that we're using Spring's
SingleConnectionDataSource as a test data source, and
specifying the HSqlDb in-memory database as the database. This
means that all it keeps the data in memory. As a result, any
changes are lost when the VM shuts down. This means we don't need
to clean up anything. But we will have to tell Hibernate to
"create-drop" the tables in the database, so Hibernate
generates them automatically when our application runs.

There is a common problem with tests that use the database: one
of our test methods left garbage in the database that made the
other tests fail. There are a few different ways of fixing it, but
here, the best is to use the "http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html">Transaction
Rollback Teardown
pattern:

[prettify]private PlatformTransactionManager transactionManager;
private TransactionStatus status;

protected void setUp() throws Exception {
    super.setUp();

    transactionManager = new HibernateTransactionManager(sessionFactory);
    TransactionDefinition txDef = new DefaultTransactionDefinition();
    status = transactionManager.getTransaction(txDef);
}

protected void tearDown() throws Exception {
    transactionManager.rollback(status);
    super.tearDown();
}
[/prettify]

Now we have a working Hibernate implementation of the
CategoryDAO and a CategoryIntegrationTest
that shows that we can display an object from a
CategoryDAO on a web page. What happens if we put
them together?

HibernateCategoryIntegrationTest

Now all the pieces work; we just have to put them together. We
create a subclass of the CategoryIntegrationTest for this. We have
to make sure to change CategoryWebIntegrationTest to
support creating something other than a
FakeCategoryDAO. Do this by changing the
populateTestdata line

CategoryDAO categoryDao =
new FakeCategoryDAO();
to
CategoryDAO categoryDao =
createCategoryDAO();
. Make createCategoryDAO()
protected so HibernateCategoryWebIntegrationTest can
override it:

[prettify]public class HibernateCategoryWebIntegrationTest extends CategoryIntegrationWebTest {

    protected CategoryDAO createCategoryDao() {
        // Copied from HibernateCategoryDAOTest
    }

}
[/prettify]

Using Real(er) DataSources

Before we continue, it's a good idea to do some refactoring. The
creation of our HibernateSessionFactory is pretty
complex. Let's put it in a separate class, named
CategorySessionFactoryBean. In both
HibernateCategoryWebIntegrationTest and
HibernateCategoryDAOTest I'd like
createCategoryDao to look like this:

[prettify]protected CategoryDAO createCategoryDAO() {
    DataSource dataSource = CategorySessionFactoryBean.inmemoryDataSource();
    SessionFactory sessionFactory = CategorySessionFactoryBean.create(dataSource);
    return new HibernateCategoryDAO(new HibernateTemplate(sessionFactory));
}
[/prettify]

Put CategorySessionFactoryBean in the main code, so
the servlet code can use it as well.

Second, I restructure the web integration test classes. Make
CategoryWebIntegrationTest abstract and create a new
FakeDAOCategoryIntegrationTest subclass that overrides
CategoryWebIntegrationTest.createCategoryDAO(). This
means that we only inherit from abstract classes. In my experience,
this leads to better structure. In this particular case, I want to
do this before I change the Spring configuration to return a
HibernateCategoryDAO.

Now we're ready to update the test WebService main
class we made to test the application interactively. We want it to
use Hibernate as well. In order to do that, let's make sure that
the normal application context uses Hibernate. First, we update
HibernateCategoryWebIntegrationTest.createCategoryDAO
to use the DAO in the Spring application context:

[prettify]protected CategoryDAO createCategoryDAO() {
    return (CategoryDAO)getWebContextBean("categoryDao");
}
[/prettify]

In order to get this to work, you will have to update how the
data source is configured in
src/main/webapp/WEB-INF/applicationContext.xml:

[prettify]<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="jdbc/primaryDs" />
  <property name="resourceRef" value="true" />
</bean>
[/prettify]

We declare the data source as a
JndiObjectFactoryBean. This means that we need to set
up the JNDI name jdbc/primaryDs before we start up
Jetty.

Jetty and JNDI

We want to get the data source from JNDI. This means that we
have to configure Jetty to give the web application a JNDI
data source. It was surprisingly hard to find the documentation for
this, but as it turns out, it is very simple. We have to create a
new org.mortbay.jetty.plus.naming.Resource object. The
constructor for this object automatically registers itself with
JNDI. In order to get the JNDI functionality to work, we must add
dependencies to jetty-plus and jetty-naming in our project's Maven
2 pom.xml file. All our Jetty dependencies should use the
same version. (Jetty developers, if you read this: please make the
dependencies from jetty-plus to jetty and jetty-naming
compile dependencies, not provided
dependencies, so I wouldn't have to do this!)

When we've added the new dependencies, we can change
CategoryWebIntegrationTest.setUp to create the
Resource for the data source before Jetty is
started.

[prettify]protected void setUp() throws Exception {
    server = new org.mortbay.jetty.Server(0);
    WebAppContext webAppContext = new WebAppContext("src/main/webapp", contextName);
    webAppContext.setConfigurationClasses(new String[] {
        // These are default, but will be overwritten if we don't specify them
        "org.mortbay.jetty.webapp.WebInfConfiguration",
        "org.mortbay.jetty.plus.webapp.Configuration",
        "org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
        "org.mortbay.jetty.webapp.TagLibConfiguration",
        // This enables the <resource-ref> section of web.xml
        "org.mortbay.jetty.plus.webapp.EnvConfiguration",
    });
    // This is needed to avoid error on subsequent Environment registrations
    org.mortbay.jetty.plus.naming.NamingEntry.setScope(NamingEntry.SCOPE_GLOBAL);
    // This actually registers the resource
    new org.mortbay.jetty.plus.naming.Resource(
                 "jdbc/primaryDs",
                 CategorySessionFactoryBean.inmemoryDataSource());
    server.start();

    int actualPort = server.getConnectors()[0].getLocalPort();
    getTestContext().setBaseUrl("http://localhost:" + actualPort + "/my-context");
}
[/prettify]

Finally, we need to update
src/main/webapp/WEB-INF/web.xml and add the
section that lets the container
know we expect a DataSource:

[prettify]<resource-ref>
  <description>Primary Data Source</description>
  <res-ref-name>jdbc/primaryDs</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>application</res-auth>
</resource-ref>
[/prettify]

This completes the wiring together of the application.

To the Server!

Of course, in production, we probably want to use a "real" database and a production server. This means exchanging the
jdbc/primaryDs resource with a connection to MySQL,
Oracle, or whatever database you use. I have created
MySqlCategoryWebIntegrationTest and
MySqlCategoryDAOTest, which uses MySQL to test. See the
companion source code for details. I also modified the
WebServer class to use MySQL.

The trickiest difference between test and production scenarios
is how to set Hibernate's HBM2DDL_AUTO property. I
have implemented this in CategorySessionFactoryBean in
the companion source code.

What about the application server?

Personally, I would strongly recommend deploying on Jetty in
production. Jetty is a production-ready server that runs critical
business. Despite the fact that it receives much less attention
than Tomcat and JBoss, Jetty runs on about "http://survey.netcraft.com/Reports/0612/">a third as many
publicly available sites as Tomcat (Apache-Coyote in the Netcraft
study).

There are two ways in which Jetty is commonly used: either you can use Jetty as a
standalone container, like Tomcat or any other, or you could build
your own Main-Class that starts up Jetty.

To deploy on a standalone Jetty instance, we can download
"http://docs.codehaus.org/display/JETTY/Downloading+and+Installing#download">
Jetty
and unpack it. Jetty lets us drop-deploy WAR files, but
we have to set up the DataSource in a jetty-env.xml file.
This should be located in
src/main/webapp/WEB-INF/jetty-env.xml in our project. See
the companion source code for an example of this file. See "http://docs.codehaus.org/display/JETTY/jetty-env.xml">Jetty's
documentation
for details on jetty-env.xml. The
"http://docs.codehaus.org/display/JETTY/DataSource+Examples">Jetty
documentation
also explains how to add DataSources for various
databases.

Use the command mvn package to build the WAR file
into the target directory of your project. Copy this
file to JETTY_HOME/webapps-plus. Start Jetty by
running

java -jar start.jar etc/jetty.xml
etc/jetty-plus.xml
from JETTY_HOME. The web
application should now be available.

The simplest way of deploying on Tomcat is to "http://tomcat.apache.org/tomcat-5.5-doc/config/host.html#Automatic%20Application%20Deployment">
add a "context" file
in Tomcat's scanning directory
($CATALINA_HOME/conf/[engine_name]/[host_name]). If you use
the location of your WAR file in the Maven repository as the
docBase for Tomcat, Tomcat will pick up any changes
when you execute mvn install. See the example context
file src/main/tomcat/web-demo.xml in the companion source
code for an example.

Conclusion, and Unfinished Business

In this article, I have showed how Jetty can help us bring
testing all the way from early development into testing your real
production environment. Running Jetty in-process in an embedded
integration test lets us control the data and context of our test
and lets us test logic related to views, as well as problems that
only show up when we put our application in a real container.
Figure 3 shows how Jetty runs inside the same VM as the tests and
is controlled from the unit test code.

<br "Embedded testing with Jetty" width="450" height="600" />
Figure 3. How the tests set up the elements of the application

This article has also given an introduction to building web
applications with Spring and Hibernate. However, there are aspects
of Spring-MVC and Hibernate that we should have covered.
Specifically, when we display more complex object graphs, we will
have to deal with having lazily loaded objects resolved while we're
rendering the view. This is handled through Spring's "http://www.springframework.org/docs/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html">
OpenSessionInViewFilter
. Introducing
OpenSessionInViewFilter will also require us to wise
up about transactions on the DAO. These problems are left as an
exercise for the reader. Leave me a comment if you would like a
hint.

I hope this article gave you some insight into how to
effectively test web application. Please, leave a comment if
something went wrong along the way, if you have discovered a better
way of doing the same thing, or if you just enjoyed the
article.

Resources

width="1" height="1" border="0" alt=" " />
Johannes Brodwall is currently lead Java architect at BBS, the company that operates Norway's banking infrastructure
Related Topics >> Testing   |