2 Replies Latest reply on Jul 7, 2011 8:44 AM by jlmaieron

    Seam Integration Tests with JUnit

    exuisitus

      I am evaluating seam for a project. IDE is Eclipse, test framework JUnit.
      The discussions about using JUnit in this forum gave some hints but not a
      really a solution. So I had do dive into the Seam documentation an cook my
      own soup, which I would like to share with the community.


      The solution is generic, thus reusable and can be packed in a library.


      It consists of two extendible classes which permit to use integration
      tests à la TestNG out of the box.


      Concept


      A sub class of AbstractSeamTest to be in turn subclassed by a test suite (test class) implementation:

      TestClass -> [JUnitSeamTest] -> AbstractSeamTest


      A class to be subclassed for running multiple test suites (Suite.class runner).
      MultipleTestClasses -> [JUnitSeamTestSuiteRunner]



      Implementation


      JUnitSeamTest.java


      import org.jboss.seam.mock.AbstractSeamTest;
      import org.junit.After;
      import org.junit.AfterClass;
      import org.junit.Before;
      import org.junit.BeforeClass;
       
      /**
       * Superclass for JUnit Seam integration tests.
       * A Seam integration test can extend this class for use with JUnit.
       * 
       * One or all of the JUnit life cycle methods can, but must not, be 
       * overwritten by the extending class. If you do, you m u s t assign the correct
       * annotation and call super.xxxx() or the static equivalent
       * JUnitSeamTest.xxxx() in the overriding method.
       * 
       * For batch processing, the Suite.class runner class sets the batch flag to true  
       * in order to prevent multiple start and stops of the embedded container while
       * executing the individual test files.
       * 
       * @author Rainer Schön
       * @date Sep 09
       */
      public class JUnitSeamTest extends AbstractSeamTest {
          // static helper instance to call non static methods in
          // a static method
          private static JUnitSeamTest seamTest = new JUnitSeamTest();
          private static boolean batch = false;
       
          /*
           *   JUnit test life cycle methods.
           */
       
          @BeforeClass
          public static  void setUpBeforeClass() throws Exception {
              if (!batch) seamTest.startContainer();
          }
       
          @AfterClass
          public static void tearDownAfterClass() throws Exception {        
              if (!batch) seamTest.stopContainer();        
          }
       
          @Before
          public void setUp() throws Exception {        
              setupClass();                
              begin();
          }
       
          @After
          public void tearDown() throws Exception {            
              end();
              cleanupClass();
          }
       
          /*
           *  Helper methods 
           */
       
          // starts the JBoss embedded ejb container
          public void startContainer() throws Exception {        
              super.startSeam();
          }
       
          // stops the JBoss embedded ejb container
          public void stopContainer() throws Exception {
              super.stopSeam();
          }
       
          public boolean isBatch() {
              return batch;
          }
       
          // used by a JUnit.Suite runner to set the batch flag 
          public void setBatch(boolean fBatch) {
              batch = fBatch;
          }
      }
      


      JUnitSeamTestSuiteRunner.java


      import org.junit.AfterClass;
      import org.junit.BeforeClass;
      import org.junit.runner.RunWith;
      import org.junit.runners.Suite;
       
      /**
       * Superclass for running Seam integration tests in batch mode. This
       * class does n o t extend JUnitSeamTest but instead creates an instance
       * to start and stop the container at the beginning and the end of the
       * processing and to set the batch flag. 
       * 
       * The subclass must only assign the @SuiteClasses({x.class, y.class})
       * annotation.
       * 
       * @author Rainer Schön
       * @Date Sep 09
       */
       
      @RunWith(Suite.class) // JUnit batch runner
      public class JUnitSeamTestSuiteRunner {
          private static JUnitSeamTest seamTest = new JUnitSeamTest();
       
          @BeforeClass
          public static  void setUpBeforeClass() throws Exception {    
              // set the flag to signal batch mode
              seamTest.setBatch(true);
              seamTest.startContainer();
          }
       
          @AfterClass
          public static void tearDownAfterClass() throws Exception {        
              seamTest.stopContainer();
              seamTest.setBatch(false);
          }
      }
      


      Examples


      Run single test suite (test class)


      import static org.junit.Assert.*;
      import java.util.List;
      import javax.faces.application.FacesMessage;
      import org.jboss.seam.faces.FacesMessages;
      import org.junit.Test;
       
      /**
       * Working example of a Seam integration test using the JUnit Seam integration
       * test port.
       * 
       */
      public class SeamIntegrationTestWithJUnit extends JUnitSeamTest {
       
          @Test
          public void testSomething() throws Exception {    
              new FacesRequest("/some.xhtml") {            
                  @Override
                  protected void updateModelValues() {
                      // do some model updates here
                      // test the results
                       assertTrue(true);
                  }
       
                  @Override
                  protected void invokeApplication() {
                      // Invoke a method and test the outcome
                       final String outcome = "fantastic";
                      assertTrue(outcome != null && outcome.equals("fantastic"));
                  }
       
                  @Override
                  protected void renderResponse() throws Exception {
                       final String msg = "This JUnit Seam Test port is great!";
                      // test some results in the render response phase
                      List<FacesMessage> messages =
                          FacesMessages.instance().getCurrentGlobalMessages();
                      messages.add(new FacesMessage(msg));
                      assertTrue(messages.size() == 1);
                      assertEquals(FacesMessage.SEVERITY_INFO,
                              messages.get(0).getSeverity());
                      assertTrue(messages.get(0).getSummary().
                              contains(msg));
                  }
              }.run();
          }
      }
      


      Run multiple test suites (test classes)


      import org.junit.runners.Suite.SuiteClasses;
       
      /**
       * Example for running multiple Seam  integration test classes at once.
       */
       
      @SuiteClasses({
              OneTest.class,  
              TwoTest.class,
              ThreeTest.class,
              NTest.class})
      public class MultipleSeamIntegrationTests extends JUnitSeamTestSuiteRunner{    
          // empty class, only used to assign @SuiteClasses annotation with
          // the array of test classes to run
      }
      


      Important


      Ensure, that the embedded JBoss container fires up correctly.
      If you use Java 6, you must start the VM with the follwing argument:

      -Dsun.lang.ClassLoader.allowArraySyntax=true


      in Eclipse put this argument in the JUnit launch configuration (box VM) for each
      test you want to run (which you will forget most of the time for the first launch!).

        • 1. Re: Seam Integration Tests with JUnit
          elfrasco

          Excellent contribution, Rainer! Tanks to you, I'm running SeamTest with JUnit4 now.


          I would like to share that I found a way to work with Jetty and Cargo instead of JBoss Embedded, simply making an small modification in your code:




          import javax.faces.FactoryFinder;
          
          import org.jboss.seam.Seam;
          import org.jboss.seam.contexts.ServletLifecycle;
          import org.jboss.seam.core.Init;
          import org.jboss.seam.init.Initialization;
          import org.jboss.seam.mock.AbstractSeamTest;
          import org.jboss.seam.mock.MockApplicationFactory;
          import org.junit.After;
          import org.junit.AfterClass;
          import org.junit.Before;
          import org.junit.BeforeClass;
          import org.junit.Test;
           
          public class JUnitSeamTest extends AbstractSeamTest {
             
              private static JUnitSeamTest seamTest = new JUnitSeamTest();
           
              @BeforeClass
              public static  void setUpBeforeClass() throws Exception {
                  seamTest.startSeam();
              }
           
              @AfterClass
              public static void tearDownAfterClass() throws Exception {       
                  seamTest.stopSeam();       
              }
           
              @Before
              public void setUp() throws Exception {       
                  setupClass();               
                  begin();
              }
           
              @After
              public void tearDown() throws Exception {           
                  end();
                  cleanupClass();
              }
             
              @Test
              public void testNothing() {
              }
           
              @Override
              protected void startSeam() throws Exception {
                  this.servletContext = createServletContext();
                  ServletLifecycle.beginApplication(this.servletContext);
                  FactoryFinder.setFactory("javax.faces.application.ApplicationFactory", MockApplicationFactory.class.getName());
                  new Initialization(this.servletContext).create().init();
                  ((Init)this.servletContext.getAttribute(Seam.getComponentName(Init.class))).setDebug(false);
              }
             
              @Override
              protected void stopSeam() throws Exception {
                  ServletLifecycle.endApplication();       
              }
          }



          The important thing is that I am overriding the startSeam() method of the AbstractSeamTest to remove the startJbossEmbeddedIfNecessary() method call. Then, in my maven2 project, I used the Cargo plugin to start and stop the Jetty web server before and after running my Seam integration tests.


          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          
              <!-- Project Information -->
          
              <dependencies>
          
                  <!-- Project dependencies -->
                         
              </dependencies>
             
              <profiles>
          
                  <!-- Profiles -->
          
              </profiles>
             
              <build>
                  <finalName>BUILD-NAME</finalName>
                  <resources>
                      <resource>               
                          <directory>src/main/resources</directory>
                          <includes>
                              <include>**/*</include>
                          </includes>
                      </resource>
                      <resource>
                          <directory>${config.resources}</directory>
                          <includes>
                              <include>**/*</include>
                          </includes>
                      </resource>
                  </resources>
                  <plugins>
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-war-plugin</artifactId>
                          <version>2.1.1</version>
                          <configuration>
                              <webResources>
                                  <resource>                           
                                      <directory>${config.resources.web}</directory>
                                      <targetPath>WEB-INF</targetPath>
                                  </resource>
                              </webResources>
                          </configuration>
                      </plugin>
                      <plugin>
                          <groupId>org.mortbay.jetty</groupId>
                          <artifactId>maven-jetty-plugin</artifactId>
                          <version>6.1.10</version>
                          <configuration> 
                              <scanIntervalSeconds>3</scanIntervalSeconds>
                              <contextPath>${project.build.finalName}</contextPath>
                          </configuration> 
                      </plugin>
          
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-surefire-plugin</artifactId>
                 
                          <!-- We only want test to run during integration-test phase -->
                          <configuration>
                              <skip>true</skip>
                          </configuration>
                 
                          <executions>
                              <execution>
                                  <id>surefire-it</id>
                                  <phase>integration-test</phase>
                                  <goals>
                                      <goal>test</goal>
                                  </goals>
                                  <configuration>
                                      <skip>false</skip>
                                      <additionalClasspathElements>
                                          <additionalClasspathElement>src/main/webapp</additionalClasspathElement>
                                      </additionalClasspathElements>
                                  </configuration>
                              </execution>
                          </executions>
                      </plugin>
                      <!-- To run the tests with Cargo -->
                      <plugin>
                          <groupId>org.codehaus.cargo</groupId>
                          <artifactId>cargo-maven2-plugin</artifactId>
                          <configuration>
                              <wait>false</wait>
                              <configuration>
                                  <deployables>
                                      <deployable>
                                          <location>${project.build.directory}/${project.build.finalName}.war</location>
                                          <type>war</type>
                                      </deployable>
                                  </deployables>
                                  <properties>
                                      <cargo.servlet.port>8081</cargo.servlet.port>
                                  </properties>
                              </configuration>
                          </configuration>
                          <executions>
                              <execution>
                                  <id>start-container</id>
                                  <phase>pre-integration-test</phase>
                                  <goals>
                                      <goal>start</goal>
                                  </goals>
                              </execution>
                              <execution>
                                  <id>stop-container</id>
                                  <phase>post-integration-test</phase>
                                  <goals>
                                      <goal>stop</goal>
                                  </goals>
                              </execution>
                          </executions>
                      </plugin>                       
          
                  </plugins>
              </build>
          </project>



          In my case, this configuration was very useful to test the web tier in isolation, connecting the Seam components with a remote instance of JBoss through JNDI. This JBoss is a server that my team and I use for integration.


          Greetings!
          Adrian.

          • 2. Re: Seam Integration Tests with JUnit
            jlmaieron
            Great solution Rainer !!

            But, I'have any errors on start JUnit test:

            ERROR [org.jboss.kernel.plugins.dependency.AbstractKernelController] Error installing to Real: name=vfsfile:/D:/wrk/proj-test/bin/ state=PostClassLoader mode=Manual requiredState=Real
            org.jboss.deployment.DeploymentException: Error during install jboss.jca:service=ManagedConnectionFactory,name=projDatasource

            Please, any help !! 

            Thanks in advance !!