34.2. Integration testing Seam components

Integration testing is more complicated. We cannot eliminate the container infrastructure, but neither do we want to deploy our application to an application server to run automated tests. Therefore, our testing environment must replicate enough container infrastructure that we can exercise the entire application, without impacting performance too heavily.
Arquillian makes it possible to run integration tests inside a real container, even without SeamTest.

Example 34.1. RegisterTest.java

                                                                                                                                                                         1
@RunWith(Arquillian)
public class RegisterTest
{                                                                                                                                                                        2
  @Deployment                                                                                                                                                            3
  @OverProtocol("Servlet 3.0")
  public static Archive<?> createDeployment()
  {                                                                                                                                                                      4
    EnterpriseArchive er = ShrinkWrap.create(ZipImporter.class).importFrom(new File("../registration-ear/target/seam-registration.ear")).as(EnterpriseArchive.class);
    return er;
  }
  @Before
  public void before()
  {
    Lifecycle.beginCall();
  }
  @After                                                                                                                                                                 5
  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());
  }
 ...
}

1

The JUnit @RunWith annotation must be present to run our tests with Arquillian.

2

Since we want to run our test in a real container, we need to specify an archive that gets deployed.

3

@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.

4

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 scratch and put in just the artifacts that we need for the test.

5

Lifecycle.beginCall() is needed to setup Seam contexts.

34.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 to run your tests on Red Hat JBoss Enterprise Application Platform, 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 Enterprise Application Platform Managed Container will automatically start the application server, provided the JBOSS_HOME environment property points to the JBoss Enterprise Application Platform 6 installation.

34.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 primarily designated for TestNG integration tests. Currently, there are some glitches so we recommend using JUnitSeamTest which is the JUnit variant of SeamTest.
The following changes must be done to run a JUnitSeamTest with Arquillian:
  • Create the @Deployment method, which constructs the test archive using ShrinkWrap. ShrinkWrap Resolver can be used to resolve any required dependencies.
  • 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 34.2. RegisterTest.java


@RunWith(Arquillian)
public class RegisterTest extends JUnitSeamTest
{
  @Deployment
  @OverProtocol("Servlet 3.0")
  public static Archive<?> createDeployment()
  {
    return Deployments.registrationDeployment(); 
  }
  @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 34.3. Deployments.java


public class Deployments
{
  public static WebArchive registrationDeployment() 
  {
    File[] libs = Maven.resolver().loadPomFromFile("pom.xml")
      .importCompileAndRuntimeDependencies()
    // resolve jboss-seam, because it is provided-scoped in the pom, but we need it bundled in the WAR
      .resolve("org.jboss.seam:jboss-seam")
      .withTransitivity().asFile();
       
    return ShrinkWrap.create(WebArchive.class, "seam-registration.war")
    // all main classes required for testing
      .addPackage(RegisterAction.class.getPackage())
    
    // classpath resources
      .addAsWebInfResource("META-INF/ejb-jar.xml", "ejb-jar.xml")
      .addAsResource("persistence.xml", "META-INF/persistence.xml")
      .addAsResource("seam.properties", "seam.properties")
   
    // resources manually copied from EAR and WAR modules
      .addAsWebInfResource("components.xml", "components.xml")
      .addAsWebInfResource("jboss-deployment-structure.xml", "jboss-deployment-structure.xml")
   
    // the modified web.xml
      .addAsWebInfResource("mock-web.xml", "web.xml")
   
    // web resources
      .addAsWebResource("index.html")
      .addAsWebResource("register.xhtml")
      .addAsWebResource("registered.xhtml")
    
    // libraries resolved using ShrinkWrap Resolver
      .addAsLibraries(libs);
  }
}

Example 34.4. 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>

34.2.2.1. Using mocks in integration tests

You may need to replace Seam components requiring resources that are unavailable in the integration test environment. For example, suppose that you use the following Seam component as a facade to some payment processing system:
@Name("paymentProcessor") 
public class PaymentProcessor 
{
  public boolean processPayment(Payment payment) { .... } 
}
For integration tests, we can make a mock component like so:
@Name("paymentProcessor")
@Install(precedence=MOCK)
public class MockPaymentProcessor extends PaymentProcessor 
{
  public boolean processPayment(Payment payment) 
  {
    return true;
  }
}
The MOCK precedence is higher than the default precedence of application components, so 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.

34.2.3. Integration testing Seam application user interactions

It is more difficult to emulate user interactions, and to place assertions appropriately. Some test frameworks let us test the whole application by reproducing user interactions with the web browser. These are useful, but not appropriate during development.
SeamTest lets you write scripted tests in a simulated JSF environment. A scripted test reproduces the interaction between the view and the Seam components, so you play the role of the JSF implementation during testing. You can test everything but the view with this approach.
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 (that is, what happens when a user clicks the Register button). We will 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()
  {
    return Deployments.registrationDeployment();
  }
  @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();
 ...
}
You have 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 will find plenty of integration tests for the Seam example applications which demonstrate more complex cases.

34.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).
If you use ant or a custom build tool which uses locally available jars - you can get all jars by running ant -f get-arquillian-libs.xml -Dtest.lib.dir=lib/test. This just downloads all Arquillian jars for managed JBoss Enterprise Application Platform container and copies those into a directory defined by the test.lib.dir property, which is lib/test in this case.
And, of course you need to put your built project and tests onto the classpath, along with the jar of 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 a seam.properties file 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.

34.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 will 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.

34.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.

Note

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 by 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.