Sample: Unit test of the JSF layer using Maven and Arquillian Warp


Inhalt:

Arquillian Warp
The JSF side: backing bean
The JSF side: Adding the backing bean to "faces-config.xml"
The JSF side: JSF page
The JSF side: Taglibs in pom.xml
Adding Arquillian Warp to pom.xml
Test class
Executing application and test
Details of Arquillian Warp


This sample is targeted for WildFly 18: it contains an EAR project, which consists of an EJB project and a Web project. The web project has a Java Server Faces page and a JSF backing bean, which makes calls to an EJB. The state of the JSF backing bean is unit tested by using the "Arquillian Warp" framework. All is done with Maven.

This sample is based completely on the sample Unit-Test der Webschicht mit Maven und Arquillian Drone and extends it with a JSF test. So take a look at this sample and its predecessing samples. Compared to the previous sample, I removed the servlet GeometricModelServlet and the tests GeometricModelBeanIT and ServletIT. So actually, only the EJB GeometricModelBean and all the "pom.xml" stuff are the same.

Here is the zipped Ecplise project for download: StatelessWarp.zip. The guide on how to import it to Eclipse is found in the Stateless Session Bean und Maven sample.


Arquillian Warp

Arquillian Warp (http://arquillian.org/arquillian-extension-warp/) is an Arquillian component which is used for testing JSF backing beans: it hooks into the JSF framework on the server side and allows validation of backing bean values during a request handling.

Some links to Arquillian Warp:
Sources on GitHub: https://github.com/arquillian/arquillian-extension-warp
Blog: http://arquillian.org/blog/tags/warp/

Surprisingly, version 1.0.0 was released in 2019, after 3 years of no further development. So let's hope that this project is not dead yet.

The Arquillian Warp unit tests helped me to create this sample. They can be found here: https://github.com/arquillian/arquillian-extension-warp/blob/master/extension/jsf-ftest/src/test/java/org/jboss/arquillian/warp/jsf/ftest/. The tests BasicJsfTest and lifecycle/TestJsfLifecycle are the most interesting ones.

To run those tests: first download the git repository. Then move into the directory "arquillian-extension-warp-master" and run the following maven commands (you have to specify a maven profile for each one).
E.g. for running them in a managed WildFly 8 server (which means that the Arquillian test runner downloads the WildFly server and starts/stops it himself):
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202
c:\path\to\apache-maven-3.6.0\bin\mvn.cmd install -Pwildfly-managed-8-0
But this did not work for me:
org.jboss.modules.ModuleNotFoundException: org.jboss.as.standalone:main
	at org.jboss.modules.ModuleLoader.loadModule(ModuleLoader.java:240)
	at org.jboss.modules.Main.main(Main.java:385)
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
So I started a WildFly 8 server locally und used the remote profile:
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202
c:\temp\apache-maven-3.6.0\bin\mvn.cmd install -Pwildfly-remote-8-0
Now the full Arquillian Warp package is build, installed to the local Maven repository, and the unit tests are executed.


The JSF side: backing bean

In the project "StatelessMaven-web", add a class de.hsrm.cs.javaee7.statelessmaven.web.GeometricModelHandler, which contains the following code:
package de.hsrm.cs.javaee7.statelessmaven.web;

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

import de.hsrm.cs.javaee7.statelessmaven.ejb.GeometricModelLocal;

@Named
@RequestScoped
public class GeometricModelHandler
{
  @EJB
  private GeometricModelLocal geometricModelLocal;

  private double dblA = 0;
  private double dblB = 0;
  private double dblC = 0;
  
  public double getA()
  {
    return dblA;
  }
  public void setA(double dblA)
  {
    this.dblA = dblA;
  }

  public double getB()
  {
    return dblB;
  }
  public void setB(double dblB)
  {
    this.dblB = dblB;
  }

  public double getC()
  {
    return dblC;
  }
  public void setC(double dblC)
  {
    this.dblC = dblC;
  }
  
  private double dblSurface = 0;
  private double dblVolume = 0;
  
  public double getVolume()
  {
    return this.dblVolume;
  }
  
  public double getSurface()
  {
    return this.dblSurface;
  } 
  
  public String calculate()
  {
    this.dblVolume = this.geometricModelLocal.computeCuboidVolume(this.dblA, this.dblB, this.dblC);
    this.dblSurface = this.geometricModelLocal.computeCuboidSurface(this.dblA, this.dblB, this.dblC);
    
    return null;
  } 
  
  @PostConstruct
  public void postContruct() 
  {
    System.out.println("GeometricModelHandler.postConstruct");
  }
  
  @Override
  public String toString()
  {
    return "a: " + this.dblA + ", b: " + this.dblB + ", c: " + this.dblC + ", volume: " + this.dblVolume + ", surface: " + this.dblSurface;
  }
}
Details of this class:

Fields/Properties:
The class has member variables and getter/setter properties for the cuboid side lengths "a", "b" and "c". It has also getter for the calculated volume and surface. The fields "a", "b" and "c" will be bound to input fields in the JSF page, and the result values will be written to output fields.

Injected EJB
The worker method uses the EJB GeometricModelBean to calculate the cuboid volume. It is in inject by the server.

Worker method
The method calculate is called when the submit button in the JSF form is clicked. It calculates the volume and surface based on the current values of "a", "b" and "c" and writes it to the fields "volume" and "surface".
The return value of this method does not matter as this simple sample does not contain navigation rules: after clicking the submit button, the same JSF page is called again.

Annotations
The class is annotated with javax.inject.Named and javax.enterprise.context.RequestScoped. Both annotations are not required to run the JSF sample, but they are necessary to run the Arquillian Warp test. If they are omitted, the test will fail. More details follow below.

toString/postConstruct
The toString method exits for debugging: it is used in the unit test to display the current state of the backing bean.
Same applies to the method annotated with javax.annotation.PostConstruct: it just does a debugging output.

The JSF side: Adding the backing bean to "faces-config.xml"

Edit "src\main\webapp\WEB-INF\faces-config.xml" (which was created as part of the archetype). Change the JSF version to "2.3" and add the backing bean:
<?xml version="1.0"?>
<faces-config version="2.3"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
	<managed-bean>
		<managed-bean-name>geometricModelHandler</managed-bean-name>
		<managed-bean-class>de.hsrm.cs.javaee7.statelessmaven.web.GeometricModelHandler</managed-bean-class>
		<managed-bean-scope>request</managed-bean-scope>
	</managed-bean>
	
</faces-config>
The "managed-bean-name" defines the name which makes the bean available in JSF pages.
The "managed-bean-scope" is "request", because we need it only per request.


The JSF side: JSF page

Add a JSP page "geometricmodel.jsp" to "src\main\webapp":
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Simple JSF sample</title>
</head>
<body>
      
<f:view>
  <h:form id="formGeometricModelInput">
    <h:panelGrid columns="3">
      
      <!-- Print calculation results only if last request was a valid calculation. -->
      <c:if test="${geometricModelHandler.volume > 0.0}"> 
        <h:outputText value="Volume:"/>
        <h:outputText id="volume" value="#{geometricModelHandler.volume}"></h:outputText>
        <h:outputText value=""></h:outputText>
      
        <h:outputText value="Surface:"/>
        <h:outputText id="surface" value="#{geometricModelHandler.surface}"></h:outputText>
        <h:outputText value=""></h:outputText>
      </c:if>
      
      <h:outputText value="Side a:"></h:outputText>
      <h:inputText label="Side A" id="a" value="#{geometricModelHandler.a}"></h:inputText>
      <h:message for="a"></h:message>
      
      <h:outputText value="Side b:"></h:outputText>
      <h:inputText label="Side B" id="b" value="#{geometricModelHandler.b}"></h:inputText>
      <h:message for="b"></h:message>
      
      <h:outputText value="Side c:"></h:outputText>
      <h:inputText label="Side C" id="c" value="#{geometricModelHandler.c}"></h:inputText>
      <h:message for="c"></h:message>
      
      <h:commandButton id="calculate" value="Calculate" action="#{geometricModelHandler.calculate}"></h:commandButton>
      <h:outputText value=""></h:outputText>
      <h:outputText value=""></h:outputText>
      
    </h:panelGrid>
  </h:form>
</f:view>
</body>
</html>
The page defines a Form "formGeometricModelInput", which has the input fields for the side lengths "a", "b" and "c". Those are bound to properties of the "geometricModelHandler" backing bean.

The button "Calculate" submits the form and calls geometricModelHandler.calculate. As this method of the backing bean returns null, the same page is displayed after submitting the form.

Now, the results are displayed: by using the JSTL tag c:if, the result fields are only shown when the current volume/surface values are greater than 0, which means that a calculation was done before.

Eclipse will not detect the three taglibs "http://java.sun.com/jsf/html", "http://java.sun.com/jsf/core" and "http://java.sun.com/jsp/jstl/core" by default (though the sample will run fine). So you have to modify "pom.xml", see next chapter.

Beware of JSTL expressions using "requestScope":

Actually, a JSTL snippet like this should work:
<c:if test="${requestScope.geometricModelHandler.volume > 0.0}">
  ...
</c:if>
But it will fail (the expression will never be true) in this sample.

This seems to be caused by those annotations on the backing bean (which are required by the unit test):
@Named
@RequestScoped
public class GeometricModelHandler
{
Probably Arquillian Warp makes changes to the "requestScope"...


The JSF side: Taglibs in pom.xml

To make Eclipse support the JSTL and JSF taglibs, add those lines to "StatelessMaven-web\pom.xml":

JSTL
	<dependencies>
        ...
        <dependency>
        	<groupId>javax.servlet</groupId>
        	<artifactId>jstl</artifactId>
        	<version>1.2</version>
        	<scope>provided</scope>
        </dependency>
        ...
	</dependencies>
The scope "provided" means that it is needed only at compile time, but the files are bundled with the WildFly server ("%WILDFLY15_HOME%\modules\system\layers\base\javax\servlet\jstl\api\main\taglibs-standard-impl-1.2.6-RC1.jar") and don't need to be added to the deployed WAR file.

JSF core/html
Those two tag libraries are a bit more complicated: They can be found in "%WILDFLY18_HOME%\modules\system\layers\base\com\sun\jsf-impl\main\jsf-impl-2.3.9.SP04.jar", which has (based on the pom.xml included in the JAR file) the maven groupId "com.sun.faces" and artifactId "jsf-impl", which should match the URL "https://repo.maven.apache.org/maven2/com/sun/faces/jsf-impl/". But version "2.3.9.SP04" is not found in Maven central - the versions here stop at "2.2.18". It can be found at the JBoss repository:
https://repository.jboss.org/nexus/content/groups/public/com/sun/faces/jsf-impl/.
So there are two ways to solve this:

Workaround: use the older version 2.2.18:
	<dependencies>
		...
		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-impl</artifactId>
			<version>2.2.18</version>
			<scope>provided</scope>
		</dependency>
		...
	</dependencies>
Better: use 2.3.9.SP04, and add the JBoss repository:
	<dependencies>
		...
		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-impl</artifactId>
			<version>2.3.9.SP04</version>
			<scope>provided</scope>
		</dependency>
		...
	</dependencies>
	
	<repositories>
		<repository>
			<id>jboss-public-repository-group</id>
			<name>JBoss Public Repository Group</name>
			<url>http://repository.jboss.org/nexus/content/groups/public/</url>
		</repository>
	</repositories>

Adding Arquillian Warp to pom.xml

First, add the BOM for Arquillian Warp:
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.jboss.arquillian.extension</groupId>
				<artifactId>arquillian-warp-bom</artifactId>
				<version>1.0.0</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
URL:
https://repo.maven.apache.org/maven2/org/jboss/arquillian/extension/arquillian-warp-bom/1.0.0/arquillian-warp-bom-1.0.0.pom


Next, add the "real" dependencies for Arquillian Warp:
	<dependencies>
		....
		
		<!-- Arquillian Warp basics: -->
		<dependency>
			<groupId>org.jboss.arquillian.extension</groupId>
			<artifactId>arquillian-warp-api</artifactId>
			<scope>test</scope>
		</dependency>
		
		<!-- Arquillian Warp basics: annotations like "BeforePhase" -->
		<dependency>
			<groupId>org.jboss.arquillian.extension</groupId>
			<artifactId>arquillian-warp-jsf</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>


Test class

As in the previous sample, this is an integration test, because the test requires the artifacts of EJB jar and Web war. So the test class name has the extension "IT" (="integration test").

The test class has one change compared to the previous sample: it now has an additional annotation org.jboss.arquillian.warp.WarpTest:
@WarpTest
@RunWith(Arquillian.class)
@RunAsClient
public class WarpIT
{
The method which uses the ShrinkWrap api to create a deployable archive (annotated with org.jboss.arquillian.container.test.api.Deployment) is the same as in my previous sample and is not explained any more.

As in the previous sample, a org.openqa.selenium.WebDriver and the deployment url are injected in member variables:
  @Drone
  private WebDriver browser;

  @ArquillianResource()
  private URL deploymentUrl;
Now it is time to do the test. Here is the full code:
import javax.inject.Inject;
import org.jboss.arquillian.warp.Activity;
import org.jboss.arquillian.warp.Inspection;
import org.jboss.arquillian.warp.Warp;
import org.jboss.arquillian.warp.jsf.AfterPhase;
import org.jboss.arquillian.warp.jsf.Phase;
import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

  ...
  @Test
  public final void browserTest() throws Exception
  {
    Warp.initiate(new Activity()
    {
      @Override
      public void perform()
      {
        browser.navigate().to(deploymentUrl.toExternalForm() + "geometricmodel.faces");
      }
    }).inspect(new Inspection()
    {
      private static final long serialVersionUID = 1L;

      // Nothing to be done...

    });

    Warp.initiate(new Activity()
    {
      public void perform()
      {
        WebElement txt = browser.findElement(By.id("formGeometricModelInput:a"));
        // Delete old value, then write new values to the form:
        txt.clear();
        txt.sendKeys("1");

        txt = browser.findElement(By.id("formGeometricModelInput:b"));
        txt.clear();
        txt.sendKeys("2");

        txt = browser.findElement(By.id("formGeometricModelInput:c"));
        txt.clear();
        txt.sendKeys("3");

        // Submit the form:
        WebElement btn = browser.findElement(By.id("formGeometricModelInput:calculate"));
        btn.click();

        WebElement result = browser.findElement(By.id("formGeometricModelInput:volume"));
        
        Assert.assertEquals("6.0", result.getText());
      }
    })
        .inspect(new Inspection()
        {
          private static final long serialVersionUID = 1L;

          @Inject
          GeometricModelHandler hmb;

          @AfterPhase(Phase.INVOKE_APPLICATION)
          public void afterInvokeApplication()
          {
            Assert.assertEquals("volume ", 6, hmb.getVolume(), 0.0);
            Assert.assertEquals("surface", 22, hmb.getSurface(), 0.0);
          }
        });
The code in detail:

Step 1: Activity
There are two calls to Warp.initiate: this method returns an org.jboss.arquillian.warp.client.execution.WarpActivityBuilder, which initializes a server request: an anonymous implementation of the org.jboss.arquillian.warp.Activity interface is provided. The interface method perform does some client side processing: using the Arquillian Drone API, you can navigate to web pages, fill forms and submit them.

The first activity in my sample just browses to the JSF page "geometricmode.jsp":
new Activity()
    {
      @Override
      public void perform()
      {
        browser.navigate().to(deploymentUrl.toExternalForm() + "geometricmodel.faces");
      }
    }
Don't call "geometricmodel.jsp", but use "geometricmodel.faces". If you call the JSP page directly, an error will occur because the JSF context is not initialized. This is done by using the ".faces" URL.

And the second activity fills the form fields, clicks the submit button and finally performs a client side assertion to check whether the results in the HTML response are valid:
new Activity()
    {
      public void perform()
      {
        WebElement txt = browser.findElement(By.id("formGeometricModelInput:a"));
        // Delete old value, then write new values to the form:
        txt.clear();
        txt.sendKeys("1");

        txt = browser.findElement(By.id("formGeometricModelInput:b"));
        txt.clear();
        txt.sendKeys("2");

        txt = browser.findElement(By.id("formGeometricModelInput:c"));
        txt.clear();
        txt.sendKeys("3");

        // Submit the form:
        WebElement btn = browser.findElement(By.id("formGeometricModelInput:calculate"));
        btn.click();

        WebElement result = browser.findElement(By.id("formGeometricModelInput:volume"));
        Assert.assertEquals("6.0", result.getText());
        
        result = browser.findElement(By.id("formGeometricModelInput:surface"));
        Assert.assertEquals("22.0", result.getText());
      }
    }
In the JSF page, you define a form "formGeometricModelInput" with input elements "a", "b" and "c" and a submit button "calculate". But in the resulting HTML, their IDs contain the full hierarchie: "formGeometricModelInput:a". So you have to use the full qualified name on the client side.


Step 2: Observation
This is not needed in my sample. But if your request returns multiple result files (e.g. images, css files or javascript files), you have to filter the relevant file, because the following inspection should only be performed on the "real" JSF page. To do so, add a call to the observe method.
The following snippet checks that the URL of the current result ends with "geometricmodel.faces" and thus is a valid JSF page:
 .observe(HttpFilters.request().uri().contains("geometricmodel.faces"))

Step 3: Inspection
With the org.jboss.arquillian.warp.client.execution.WarpActivityBuilder (or the result of the observe method), you perform the server side testing code by calling the method inspect. The parameter is an anonymous implementation of the interface org.jboss.arquillian.warp.Inspection. All code in this inspection is executed on the server side. Note the console outputs in my sample.
.inspect(new Inspection()
    {
      private static final long serialVersionUID = 1L;

      // ...
    });
The "serialVersionUID" must be defined on each Inspection implementation. Otherwise, there will be a client side error on test execution:

Unable to transform inspection de.hsrm.cs.javaee7.statelessmaven.web.test.WarpIT$1:
serialVersionUID for class de.hsrm.cs.javaee7.statelessmaven.web.test.WarpIT$1 is not set; please set serialVersionUID to allow Warp work correctly
Caused by: org.jboss.arquillian.warp.impl.client.transformation.NoSerialVersionUIDException: serialVersionUID for class de.hsrm.cs.javaee7.statelessmaven.web.test.WarpIT$1 is not set; please set serialVersionUID to allow Warp work correctly

In the Inspection implementation, you can add several methods used for testing. They all are annotated with either org.jboss.arquillian.warp.jsf.BeforePhase or org.jboss.arquillian.warp.jsf.AfterPhase. This annotation tells Arquillian Warp at which JSF lifecycle phase the method should be called.

          @BeforePhase(Phase.RENDER_RESPONSE)
          public void beforeRenderResponse()
          {
            ...
          }

          @AfterPhase(Phase.RENDER_RESPONSE)
          public void afterRenderResponse()
          {
            ...
          }
The org.jboss.arquillian.warp.jsf.Phase enumeration has these values, which correspond to the JSF lifecycle:
My sample defines methods for all "before" and "after" steps and just prints the state of the backing bean. This shows when the form values are written to the bean fields and when the processing of the "submit" button click is done.

Note: you can also annotate methods with org.jboss.arquillian.warp.servlet.BeforeServlet and org.jboss.arquillian.warp.servlet.AfterServlet. Those are more related to servlets.

Step 3a: Testing
Depending on the phase, you can test the state of e.g. the FacesContext or custom JSF backing beans. To do so, inject the bean in the Inspection implementation:
          @Inject
          GeometricModelHandler hmb;
Now, you can check e.g. the properties "volume" and "surface" of the backing bean (which is done here after the INVOKE_APPLICATION phase - quite late in the lifecycle, but this is the first point where the "submit" button click is handled and the resulting values can be found in the backing bean):
          @AfterPhase(Phase.INVOKE_APPLICATION)
          public void afterInvokeApplication()
          {
            Assert.assertEquals("invalid volume", 6, hmb.getVolume(), 0.0);
            Assert.assertEquals("invalid surface", 22, hmb.getSurface(), 0.0);
          }
Here, a volume of 6 and a surface of 22 are expected.
If an assertion fails, the maven test runner will print the error message.


Don't do this: multiple requests in one call to Warp.initiate:

Assume that you write your test code like this:
    Warp.initiate(new Activity()
    {
      @Override
      public void perform()
      {
        browser.navigate().to(deploymentUrl.toExternalForm() + "geometricmodel.faces");
      
        WebElement txt = browser.findElement(By.id("formGeometricModelInput:a"));
		
        // fill form data...

        // Submit the form:
        WebElement btn = browser.findElement(By.id("formGeometricModelInput:calculate"));
        btn.click();

        // check result values...
      }
    })
        .inspect(new Inspection()
        {
          private static final long serialVersionUID = 1L;

          @AfterPhase(Phase.INVOKE_APPLICATION)
          public void afterInvokeApplication()
          {
            // do testing...
          }
        });
This code (which seems to be identical to my working sample) will result in this exception:

org.jboss.arquillian.warp.impl.client.verification.InspectionMethodWasNotInvokedException: Lifecycle test declared on public void de.hsrm.cs.javaee7.statelessmaven.web.test.WarpIT$1.afterInvokeApplication() with qualifiers [@org.jboss.arquillian.warp.jsf.AfterPhase(value=INVOKE_APPLICATION)] was not executed

The reason seems to be: we have two server calls: the first initial request to the JSF page, and the second request when submitting the form.

It seems that Arquillian Warp hooks into the first call, where not all lifecycle phases are passed through.


The annotations on the JSF backing bean:

As I wrote above, our JSF backing bean needs two annotations javax.inject.Named and javax.enterprise.context.RequestScoped.

If they are not present, the injection of the bean in the test Inspection will show wrong results: the bean values (JSF parameters "a", "b" and "c" and calculated values "volume" and "surface") will always be "0".
When adding a javax.annotation.PostConstruct annotation to the bean and do some System.out.println, you will see that this method is called quite often, actually before each phase method. When the annotations are present, it is called only once in the beginning of the request.

I don't know the reason for this...

Documentation for javax.enterprise.context.RequestScoped:
https://javaee.github.io/javaee-spec/javadocs/javax/enterprise/context/RequestScoped.html
https://javaee.github.io/tutorial/injection002.html

You can set a value for the javax.inject.Named annotation: it must match the name of the bean specified in "faces-config.xml". Actually, the value must be set when the name of the bean does not match the rule "class name, but first character is lower case"
@Named(value="myGeometricModelHandler")
@RequestScoped
public class GeometricModelHandler
{


Executing application and test

To deploy and run the JSF application: see sample Stateless Session Bean und Maven: run the goals clean install wildfly:deploy and launch either the profile arq-managed or arq-remote. Ideally, set the option "Skip tests" in the launch configuration.
Then call the URL http://localhost:8080/StatelessMaven-web/geometricmodel.faces (don't call ".../geometricmodel.jsp" - this will result in an error message because the faces context is not initialized).

Don't forget to undeploy the application afterwards: run the goal wildfly:undeploy using the same profile as on deployment.


To run the tests: See sample Unit-Test mit Maven und Arquillian: execute the goal verify (as my WarpIT is an integration test), and launch either the profile arq-managed or arq-remote.
To sum it up: both profiles are defined in "StatelessMaven-web\pom.xml". They use the maven-failsafe-plugin because of the integration tests. The file "StatelessMaven-web\src\test\resources\arquillian.xml" defines further details about the WildFly server configuration. Most important is this line:
	<defaultProtocol type="Servlet 3.0" />
This line is a prerequesite for Arquillian Warp to run.


Details of Arquillian Warp

We now take a look at the deployed application. To do so, add a Thread.sleep statement at any part of the test method. Now, you can open "standalone.xml" and search for the path of the deployed application:

    <deployments>
        <deployment name="StatelessMaven-ear.ear" runtime-name="StatelessMaven-ear.ear">
            <content sha1="776227df79bb45cc6cfe22d06689077ec35b9ab3"/>
        </deployment>
    </deployments>
This maps to the path "%WILDFLY_HOME%\standalone\data\content\77\6227df79bb45cc6cfe22d06689077ec35b9ab3\content". Here, you will find a file "content". Rename it to "StatelessMaven-ear.ear" and open it with a zip tool.
See the sample
Beispiel: Unit-Test mit Maven und Arquillian for a basic analysis. The EAR file content is the same, but the war file looks different:
WAR content
Besides the well known "arquillian-protocol.jar", which creates a servlet that tunnels Arquillian calls, three more "arquillian-warp" JARs are added. When taking a look at the file "arquillian-warp-jsf.jar", you find a "META-INF\faces-config.xml" file: this one adds a Arquillian Warp specifiy PhaseListener and a FacesContextFactory.

Here is a description of the way Arquillian Warp hooks into a request: https://github.com/lfryc/arquillian.github.com/blob/warp-docs/docs/warp.adoc#warp-request-processing

Quoted from this page:
In order to hook into client-to-server communication, Warp puts a HTTP proxy in between.

This proxy observes requests incoming from a client and then enhances a request with a payload required for a server inspection (processed reffered to as "piggy-backing on a request").

Once an enhanced request enters a server, it is blocked by a request filter and an inspection is registered into an Arquillian system. The Warp’s filter then handles the processing to the traditional request processing.

During a requst processing lifecycle, the Warp listens for appropriate lifecycle hooks and as a response, it can execute arbitrary actions which inspects a state of the request context.

To help with a full-featured verification, a Warp’s inspection process can leverage Arquillian’s dependency injection system.

Once the request is processed by the server, leading into committing response, Warp can collect a result of inspection and enhance a built response to the client (again using piggy-backing method).

The Warp’s proxy evaluates the response and either reports a failure (in case of server failure) or continues with execution of the test.

One thing that irritated me first: after running the tests, a directory "StatelessMaven\StatelessMaven-web\lib\bin\lib\amd64-Windows-gpp\jni" appears, with three DLLs inside ("barchart-udt-core-2.3.0-SNAPSHOT.dll", "libgcc_s_sjlj_64-1.dll", "libstdc++_64-6.dll"). The first one ("barchart-udt") seems to be a "Java wrapper around native C++ UDT protocol implementation" (https://github.com/barchart/barchart-udt/wiki), and it includes the two other DLLs (see https://github.com/barchart/barchart-udt/blob/master/barchart-udt-core/pom.xml). It seems the "barchart-udt" JAR is introduced by Netty ("an asynchronous event-driven network application framework"), and this one is probably a dependency of Arquillian Warp.



Last modified 26.01.2020
History:
09.02.2019: created
26.01.2020: re-created the project based on the archetype "wildfly-jakartaee-ear-archetype", profile "arq-managed" starts the server by using the "JBOSS_HOME" variable, updated to WildFly 18, updated to from Arquillian Warp 1.0.0.Alpha8 to 1.0.0.