SeamFramework.orgCommunity Documentation

Chapter 38. Testing Seam applications

38.1. Unit testing Seam components
38.2. Integration testing Seam components
38.2.1. Configuration
38.2.2. Using JUnitSeamTest with Arquillian
38.2.3. Integration testing Seam application user interactions

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.

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.

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

(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);
      WebArchive web = er.getAsType(WebArchive.class, "registration-web.war");
(5)      web.addClasses(RegisterTest.class);
      return er;
   }
   @Before
   public void before()
   {
(6)       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());
   }
   ...
}

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

5

The test class itself must be added to the web archive.

6

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


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.

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

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();
   }
   ...
}


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:

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.


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

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.