JBoss.com Enterprise Documentation

- Prev
- Next

## Chapter 31. Testing Seam applications

Most Seam applications will need at least two kinds of automated tests: unit tests, which test a particular Seam component in isolation, and scripted integration tests which exercise all Java layers of the application (that is, everything except the view pages).

Both kinds of tests are very easy to write.

## 31.1. Unit testing Seam components

All Seam components are POJOs. This is a great place to start if you want easy unit testing. And since Seam emphasises the use of bijection for inter-component interactions and access to contextual objects, it's very easy to test a Seam component outside of its normal runtime environment.

Consider the following Seam Component which creates a statement of account for a customer:

    @Stateless

    @Scope(EVENT)

    @Name("statementOfAccount")

    public class StatementOfAccount {

       @In(create=true) EntityManager entityManager

       private double statementTotal;

       @In

       private Customer customer;

       @Create

       public void create() {

          List<Invoice> invoices = entityManager

             .createQuery("select invoice from Invoice invoice where invoice.customer = :customer")

             .setParameter("customer", customer)

             .getResultList();

          statementTotal = calculateTotal(invoices);

       }

       public double calculateTotal(List<Invoice> invoices) {

          double total = 0.0;

          for (Invoice invoice: invoices)

          {

             double += invoice.getTotal();

          }

          return total;

       }

       // getter and setter for statementTotal

    }

We could write a unit test for the calculateTotal method (which tests the business logic of the component) as follows:

    public class StatementOfAccountTest {

        @Test

        public testCalculateTotal {

           List<Invoice> invoices = generateTestInvoices(); // A test data generator

           double statementTotal = new StatementOfAccount().calculateTotal(invoices);

           assert statementTotal = 123.45;

        }

    }

You'll notice we aren't testing retrieving data from or persisting data to the database; nor are we testing any functionality provided by Seam. We are just testing the logic of our POJOs. Seam components don't usually depend directly upon container infrastructure, so most unit testing as as easy as that!

However, if you want to test the entire application, read on.

## 31.2. Integration testing Seam components

Integration testing is slightly more difficult. In this case, we can't eliminate the container infrastructure; indeed, that is part of what is being tested! At the same time, we don't want to be forced to deploy our application to an application server to run the automated tests. We need to be able to reproduce just enough of the container infrastructure inside our testing environment to be able to exercise the whole application, without hurting performance too much.

The approach taken by Seam is to let you write tests that exercise your components while running inside a pruned down container environment (Seam, together with the JBoss Embedded container; n.b. JBoss Embedded requires JDK 1.5 and does not work with JDK 1.6).

    public class RegisterTest extends SeamTest

    {

       @Test

       public void testRegisterComponent() throws Exception

       {

          new ComponentTest() {

             protected void testComponents() throws Exception

             {

                setValue("#{user.username}", "1ovthafew");

                setValue("#{user.name}", "Gavin King");

                setValue("#{user.password}", "secret");

                assert invokeMethod("#{register.register}").equals("success");

                assert getValue("#{user.username}").equals("1ovthafew");

                assert getValue("#{user.name}").equals("Gavin King");

                assert getValue("#{user.password}").equals("secret");

             }

          }.run();

       }

       ...

    }

### 31.2.1. Using mocks in integration tests

Occasionally, we need to be able to replace the implementation of some Seam component that depends upon resources which are not available in the integration test environment. For example, suppose we have some Seam component which is a facade to some payment processing system:

    @Name("paymentProcessor")

    public class PaymentProcessor {

        public boolean processPayment(Payment payment) { .... }

    }

For integration tests, we can mock out this component as follows:

    @Name("paymentProcessor")

    @Install(precedence=MOCK)

    public class MockPaymentProcessor extends PaymentProcessor {

        public boolean processPayment(Payment payment) {

            return true;

        }

    }

Since the `MOCK` precedence is higher than the default precedence of application components, Seam will install the mock implementation whenever it is in the classpath. When deployed into production, the mock implementation is absent, so the real component will be installed.

## 31.3. Integration testing Seam application user interactions

An even harder problem is emulating user interactions. A third problem is where to put our assertions. Some test frameworks let us test the whole application by reproducing user interactions with the web browser. These frameworks have their place, but they are not appropriate for use at development time.

`SeamTest` lets you write scripted tests, in a simulated JSF environment. The role of a scripted test is to reproduce the interaction between the view and the Seam components. In other words, you get to pretend you are the JSF implementation!

This approach tests everything except the view.

Let's consider a JSP view for the component we unit tested above:

    <html>

     <head>

      <title>Register New User</title>

     </head>

     <body>

      <f:view>

       <h:form>

         <table border="0">

           <tr>

             <td>Username</td>

             <td><h:inputText value="#{user.username}"/></td>

           </tr>

           <tr>

             <td>Real Name</td>

             <td><h:inputText value="#{user.name}"/></td>

           </tr>

           <tr>

             <td>Password</td>

             <td><h:inputSecret value="#{user.password}"/></td>

           </tr>

         </table>

         <h:messages/>

         <h:commandButton type="submit" value="Register" action="#{register.register}"/>

       </h:form>

      </f:view>

     </body>

    </html>

We want to test the registration functionality of our application (the stuff that happens when the user clicks the Register button). We'll reproduce the JSF request lifecycle in an automated TestNG test:

    public class RegisterTest extends SeamTest

    {

       @Test

       public void testRegister() throws Exception

       {

          new FacesRequest() {

             @Override

             protected void processValidations() throws Exception

             {

                validateValue("#{user.username}", "1ovthafew");

                validateValue("#{user.name}", "Gavin King");

                validateValue("#{user.password}", "secret");

                assert !isValidationFailure();

             }

             @Override

             protected void updateModelValues() throws Exception

             {

                setValue("#{user.username}", "1ovthafew");

                setValue("#{user.name}", "Gavin King");

                setValue("#{user.password}", "secret");

             }

             @Override

             protected void invokeApplication()

             {

                assert invokeMethod("#{register.register}").equals("success");

             }

             @Override

             protected void renderResponse()

             {

                assert getValue("#{user.username}").equals("1ovthafew");

                assert getValue("#{user.name}").equals("Gavin King");

                assert getValue("#{user.password}").equals("secret");

             }

          }.run();

       }

       ...

    }

Notice that we've extended `SeamTest`, which provides a Seam environment for our components, and written our test script as an anonymous class that extends `SeamTest.FacesRequest`, which provides an emulated JSF request lifecycle. (There is also a `SeamTest.NonFacesRequest` for testing GET requests.) We've written our code in methods which are named for the various JSF phases, to emulate the calls that JSF would make to our components. Then we've thrown in various assertions.

You'll find plenty of integration tests for the Seam example applications which demonstrate more complex cases. There are instructions for running these tests using Ant, or using the TestNG plugin for eclipse:

### 31.3.1. Configuration

If you used seam-gen to create your project you are ready to start writing tests. Otherwise you'll need to setup the testing environment in your favorite build tool (e.g. ant, maven, eclipse).

First, lets look at the dependencies you need at a minimum:

Table 31.1.

Group Id | Artifact Id | Location in Seam
--- | --- | ---
`org.jboss.seam.embedded` | `hibernate-all` | `lib/test/hibernate-all.jar`
`org.jboss.seam.embedded` | `jboss-embedded-all` | `lib/test/jboss-embedded-all.jar`
`org.jboss.seam.embedded` | `thirdparty-all` | `lib/test/thirdparty-all.jar`
`org.jboss.seam.embedded` | `jboss-embedded-api` | `lib/jboss-embedded-api.jar`
`org.jboss.seam` | `jboss-seam` | `lib/jboss-seam.jar`
`org.jboss.el` | `jboss-el` | `lib/jboss-el.jar`
`javax.faces` | `jsf-api` | `lib/jsf-api.jar`
`javax.activation` | `javax.activation` | `lib/activation.jar`

It's very important you don't put the compile time JBoss AS dependencies from `lib/` (e.g. `jboss-system.jar`) on the classpath, these will cause Embedded JBoss to not boot. So, just add the dependencies (e.g. Drools, jBPM)you need as you go.

You also need to include the `bootstrap/` directory on the classpath; `bootstrap/` contains the configuration for Embedded JBoss.

And, of course you need to put your built project and tests onto the classpath. Don't forget to put all the correct configuration files for JPA and Seam onto the classpath as well.Seam asks Embedded JBoss to deploy any resource (jar or directory) which has `seam.properties` in it's root. Therefore, if you don't assemble a directory structure that resembles a deployable archive containing your built project, you must put a `seam.properties` in each resource.

By default, a generated project will use the `java:/DefaultDS` (a built in HSQL datasource in Embedded JBoss) for testing. If you want to use another datasource place the `foo-ds.xml` into `bootstrap/deploy` directory.

### 31.3.2. Using SeamTest with another test framework

Seam provides TestNG support out of the box, but you can also use another test framework, such as JUnit, if you want.

You'll need to provide an implementation of `AbstractSeamTest` which does the following:

- Calls `super.begin()` before every test method.

- Calls `super.end()` after every test method.

- Calls `super.setupClass()` to setup integration test environment. This should be called before any test methods are called.

- Calls `super.cleanupClass()` to clean up the integration test environment.

- Calls `super.startSeam()` to start Seam at the start of integration testing.

- Calls `super.stopSeam()` to cleanly shut down Seam at the end of integration testing.

### 31.3.3. Integration Testing with Mock Data

If you need to insert or clean data in your database before each test you can use Seam's integration with DBUnit. To do this, extend DBUnitSeamTest rather than SeamTest.

You need to provide a dataset for DBUnit. IMPORTANT NOTE: DBUnit supports two formats for dataset files, flat and XML. Seam's DBUnitSeamTest assumes the flat format is used, so please ensure that your dataset is in this format also.

    <dataset>

       <ARTIST

          id="1"

          dtype="Band"

          name="Pink Floyd" />

       <DISC

          id="1"

          name="Dark Side of the Moon"

          artist_id="1" />

    </dataset>

and tell Seam about it by overriding `prepareDBUnitOperations()`:

    protected void prepareDBUnitOperations() {

        beforeTestOperations.add(

           new DataSetOperation("my/datasets/BaseData.xml")

        );

     }

`DataSetOperation` defaults to `DatabaseOperation.CLEAN_INSERT` if no other operation is specified as a constructor argument. The above example cleans all tables defined `BaseData.xml`, then inserts all rows declared in `BaseData.xml` before each `@Test` method is invoked.

If you require extra cleanup after a test method executes, add operations to `afterTestOperations` list.

You need to tell DBUnit about the datasource you are using by setting a TestNG test parameter named `datasourceJndiName`:

       <parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>

DBUnitSeamTest only works out of the box with HSQL as a datasource. If you want to use another database, then you'll need to implement some extra methods. Read the javadoc on `DBUnitSeamTest` for more.

### 31.3.4. Integration Testing Seam Mail

Warning! This feature is still under development.

It's very easy to integration test your Seam Mail:

    public class MailTest extends SeamTest {

       @Test

       public void testSimpleMessage() throws Exception {

          new FacesRequest() {

             @Override

             protected void updateModelValues() throws Exception {

                setValue("#{person.firstname}", "Pete");

                setValue("#{person.lastname}", "Muir");

                setValue("#{person.address}", "test@example.com");

             }

             @Override

             protected void invokeApplication() throws Exception {

                MimeMessage renderedMessage = getRenderedMailMessage("/simple.xhtml");

                assert renderedMessage.getAllRecipients().length == 1;

                InternetAddress to = (InternetAddress) renderedMessage.getAllRecipients()[0];

                assert to.getAddress().equals("test@example.com");

             }

          }.run();

       }

    }

We create a new `FacesRequest` as normal. Inside the invokeApplication hook we render the message using `getRenderedMailMessage(viewId);`, passing the viewId of the message to render. The method returns the rendered message on which you can do your tests. You can of course also use any of the standard JSF lifecycle methods.

There is no support for rendering standard JSF components so you can't test the content body of the mail message easily.

- PrevChapter 30. JBoss EL
- Top of page
- Front page
- NextChapter 32. Seam tools