SeamFramework.org Community Documentation

- Prev
- Next

## Chapter 38. 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.

## 38.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 are as easy as that!

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

## 38.2. Integration testing Seam components

## Warning

Using JBoss Embedded for integration testing was removed. Seam uses Arquillian with JUnit. Right now TestNG is not recommended test framework with Arquillian.

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 AS container)

Arquillian makes it possible to run integration tests inside a real container, even without `SeamTest`.

Example 38.1. RegisterTest.java

    @RunWith(Arquillian)

    public class RegisterTest

    {

       @Deployment

       @OverProtocol("Servlet 3.0")

       public static Archive<?> createDeployment()

       {

          EnterpriseArchive er = ShrinkWrap.create(ZipImporter.class)

             .importFrom(new File("../registration-ear/target/seam-registration.ear"))

             .as(EnterpriseArchive.class);

          WebArchive web = er.getAsType(WebArchive.class, "registration-web.war");

          web.addClasses(RegisterTest.class);

          return er;

       }

       @Before

       public void before()

       {

           Lifecycle.beginCall();

       }

       @After

       public void after(

       {

           Lifecycle.endCall();

       }

       protected void setValue(String valueExpression, Object value)

       {

          Expressions.instance().createValueExpression(valueExpression).setValue(value);

       }

       @Test

       public void testRegisterComponent() throws Exception

       {

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

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

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

         Register register = (Register)Component.getInstance("register");

         Assert.assertEquals("success", register.register());

       }

       ...

    }

| The JUnit `@RunWith` annotation must be present to run our tests with Arquillian.
| Since we want to run our test in a real container, we need to specify an archive that gets deployed.
| `@OverProtocol` is an Arquillian annotation to specify the protocol used for running the tests. The "Servlet 3.0" protocol is the recommended protocol for running Seam tests.
| ShrinkWrap can be used to create the deployment archive. In this example, the whole EAR is imported, but we could also use the ShrinkWrap API to create a WAR or an EAR from the scratch and put in just the artifacts that we need for the test.
| The test class itself must be added to the web archive.
| `Lifecycle.beginCall()` is needed to setup Seam contexts.

### 38.2.1. Configuration

The Arquillian configuration depends on the specific container used. See Arquillian documentation for more information.

Assuming you are using Maven as your build tool and want run your tests on JBoss AS 7, you will need to put these dependencies into your `pom.xml`:

    <dependency>

        <groupId>org.jboss.arquillian.junit</groupId>

        <artifactId>arquillian-junit-container</artifactId>

        <version>${version.arquillian}</version>

        <scope>test</scope>

    </dependency>

    <dependency>

        <groupId>org.jboss.as</groupId>

        <artifactId>jboss-as-arquillian-container-managed</artifactId>

        <version>${version.jboss.as7}</version>

        <scope>test</scope>

    </dependency>

The Arquillian JBoss AS Managed Container will automatically start the application server, provided the JBOSS_HOME environment property points to the JBoss AS 7 installation.

### 38.2.2. Using JUnitSeamTest with Arquillian

It is also possible to use the simulated JSF environment provided by `SeamTest` along with Arquillian. This is useful especially if you are migrating from previous Seam releases and want to keep your existing testsuite mostly unchanged.

## Note

SeamTest was primary designated for TestNG integration tests. There are some glitches so we recommend to use JUnitSeamTest which is the JUnit variant for SeamTest.

The following changes must be done to run a JUnitSeamTest with Arquillian:

- Create the `@Deployment` method.

- Convert the test to JUnit. A `JUnitSeamTest` class can now be used instead of the original `SeamTest`.

- Replace the `SeamListener` with `org.jboss.seam.mock.MockSeamListener` in web.xml.

Example 38.2. RegisterTest.java

    @RunWith(Arquillian)

    public class RegisterTest extends JUnitSeamTest

    {

       @Deployment

       @OverProtocol("Servlet 3.0")

       public static Archive<?> createDeployment()

       {

          EnterpriseArchive er = ShrinkWrap.create(ZipImporter.class)

             .importFrom(new File("../registration-ear/target/seam-registration.ear"))

             .as(EnterpriseArchive.class);

          WebArchive web = er.getAsType(WebArchive.class, "registration-web.war");

          web.addClasses(RegisterTest.class);

          // Replacing the SeamListener with MockSeamListener

          web.delete("/WEB-INF/web.xml");

          web.addAsWebInfResource("WEB-INF/mock-web.xml", "web.xml");

          return er;

       }

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

       }

       ...

    }

Example 38.3. mock-web.xml

    <?xml version="1.0" ?>

    <web-app version="3.0"

             xmlns="http://java.sun.com/xml/ns/javaee"

             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

        <listener>

            <listener-class>org.jboss.seam.mock.MockSeamListener</listener-class>

        </listener>

    </web-app>

#### 38.2.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.

### 38.2.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` or `JUnitSeamTest` 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 JSF 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 JUnit test:

    @RunWith(Arquillian.class)

    public class RegisterTest extends JUnitSeamTest

    {

       @Deployment(name="RegisterTest")

       @OverProtocol("Servlet 3.0")

       public static Archive<?> createDeployment()

       {

         EnterpriseArchive er = ShrinkWrap.create(ZipImporter.class, "seam-registration.ear").importFrom(new File("../registration-ear/target/seam-registration.ear"))

                   .as(EnterpriseArchive.class);

         WebArchive web = er.getAsType(WebArchive.class, "registration-web.war");

         web.addClasses(RegisterTest.class);

         // Install org.jboss.seam.mock.MockSeamListener

         web.delete("/WEB-INF/web.xml");

         web.addAsWebInfResource("web.xml");

         return er;

      }

       @Test

       public void testLogin() throws Exception

       {

          new FacesRequest("/register.xhtml") {

             @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("/registered.xhtml");

                setOutcome("/registered.xhtml");

             }

             @Override

             protected void afterRequest()

             {

                assert isInvokeApplicationComplete();

                assert !isRenderResponseBegun();

             }

          }.run();

          ...

    }

Notice that we've extended `JUnitSeamTest`, which provides a Seam environment for our components, and written our test script as an anonymous class that extends `JUnitSeamTest.FacesRequest`, which provides an emulated JSF request lifecycle. (There is also a `JUnitSeamTest.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 Maven, or using the JUnit plugin for eclipse:

#### 38.2.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).

For ant or own build tool which uses jars on local - you can use to get all jars by running `ant -f get-arquillian-libs.xml -Dtest.lib.dir=lib/test`. This just copy all Arquillian jars for managed JBoss AS 7.1.1 container and copy all jars into defined directory `lib/test` by using that `test.lib.dir` property.

And, of course you need to put your built project and tests onto the classpath as well as jar for your test framework. Don't forget to put all the correct configuration files for JPA and Seam onto the classpath as well. Seam asks Arquillian 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.

#### 38.2.3.2. Using SeamTest with another test framework

Seam provides JUnit support out of the box, but you can also use another test framework, 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.

#### 38.2.3.3. Integration Testing with Mock Data

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

You have to provide a dataset for DBUnit.

## Caution

DBUnit supports two formats for dataset files, flat and XML. Seam's `DBJUnitSeamTest` assumes the flat format is used, so make sure that your dataset is in this format.

    <dataset>

       <ARTIST

          id="1"

          dtype="Band"

          name="Pink Floyd" />

       <DISC

          id="1"

          name="Dark Side of the Moon"

          artist_id="1" />

    </dataset>

In your test class, configure your dataset with overriding `prepareDBUnitOperations()`:

    protected void prepareDBUnitOperations() {

        setDatabase("HSQL");

        setDatasourceJndiName("java:/jboss/myDatasource");

        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 which datasource you are using. This is accomplished by calling `setDatasourceJndiName`.

DBJUnitSeamTest has support for MySQL and HSQL - you need to tell it which database is being used, otherwise it defaults to HSQL.

It also allows you to insert binary data into the test data set (n.b. this is untested on Windows). You need to tell it where to locate these resources on your classpath:

    setBinaryUrl("images/");

You do not have to configure any of these parameters except the `datasourceJndiName` if you use HSQL and have no binary imports. You have to call `setDatabaseJndiName()` before your test runs. If you are not using HSQL or MySQL, you need to override some methods. See the Javadoc of `DBJUnitSeamTest` for more details.

#### 38.2.3.4. Integration Testing Seam Mail

## Caution

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 37. Performance Tuning
- Top of page
- Front page
- NextChapter 39. Dependencies