Where the
integration test
invokes the SampleService
’s add
method directly, the functional test invokes
the add
method indirectly using a web browser. The Arquillian Blade Example’s
functional tests interact with the portlet UI to verify content and validate
behavior. The test classes are in the src/testIntegration/java
folder and test
resources are in the src/testIntegration/resources
folder.
The example functional tests operate on the following view parameters:
firstParameter
: First number to addsecondParameter
: Second number to addresult
: Sum of the two numbers
The JSP file view.jsp
and portlet class BasicPortletFunctionalTest
use these
parameters. Here’s the view.jsp
code:
<%@ include file="/init.jsp" %>
<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>
<%
int firstParameter = ParamUtil.getInteger(request, "firstParameter", 1);
int secondParameter = ParamUtil.getInteger(request, "secondParameter", 1);
int result = ParamUtil.getInteger(request, "result");
%>
<portlet:actionURL name="add" var="portletURL" />
<p>
<b>Sample Portlet is working!</b>
</p>
<aui:form action="<%= portletURL %>" method="post" name="fm">
<aui:input inlineField="<%= true %>" label="" name="firstParameter" size="4" type="int" value="<%= firstParameter %>" />
<span> + </span>
<aui:input inlineField="<%= true %>" label="" name="secondParameter" size="4" type="int" value="<%= secondParameter %>" />
<span> = </span>
<span class="result"><%= result %></span>
<aui:button type="submit" value="add" />
</aui:form>
Users enter numbers in the firstParameter
and secondParameter
input fields
and click on the add
button to show the sum to the result
field.
Functional test class BasicPortletFunctionalTest
uses Selenium to interact
with the portlet’s UI. Here’s the BasicPortletFunctionalTest
class:
package com.liferay.arquillian.test;
import com.google.common.io.Files;
import com.liferay.arquillian.portal.annotation.PortalURL;
import com.liferay.portal.kernel.exception.PortalException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@RunAsClient
@RunWith(Arquillian.class)
public class BasicPortletFunctionalTest {
@Deployment
public static JavaArchive create() throws Exception {
final File tempDir = Files.createTempDir();
String gradlew = "./gradlew";
String osName = System.getProperty("os.name", "");
if (osName.toLowerCase().contains("windows")) {
gradlew = "./gradlew.bat";
}
final ProcessBuilder processBuilder = new ProcessBuilder(
gradlew, "jar", "-Pdir=" + tempDir.getAbsolutePath());
final Process process = processBuilder.start();
process.waitFor();
final File jarFile = new File(
tempDir.getAbsolutePath() +
"/com.liferay.arquillian.sample-1.0.0.jar");
return ShrinkWrap.createFromZipFile(JavaArchive.class, jarFile);
}
@Test
public void testAdd()
throws InterruptedException, IOException, PortalException {
_browser.get(_portlerURL.toExternalForm());
_firstParamter.clear();
_firstParamter.sendKeys("2");
_secondParameter.clear();
_secondParameter.sendKeys("3");
_add.click();
Thread.sleep(5000);
Assert.assertEquals("5", _result.getText());
}
@Test
public void testInstallPortlet() throws IOException, PortalException {
_browser.get(_portlerURL.toExternalForm());
final String bodyText = _browser.getPageSource();
Assert.assertTrue(
"The portlet is not well deployed",
bodyText.contains("Sample Portlet is working!"));
}
@FindBy(css = "button[type=submit]")
private WebElement _add;
@Drone
private WebDriver _browser;
@FindBy(css = "input[id$='firstParameter']")
private WebElement _firstParamter;
@PortalURL("arquillian_sample_portlet")
private URL _portlerURL;
@FindBy(css = "span[class='result']")
private WebElement _result;
@FindBy(css = "input[id$='secondParameter']")
private WebElement _secondParameter;
}
Arquillian annotation @RunAsClient
and JUnit annotation @RunWith
mark the
class as a web client that runs on Arquillian.
Similar to the integration test class, this class’s create
method packages the
test as a JAR file for Arquillian to execute.
Method testInstallPortlet
verifies portlet content.
@Test
public void testInstallPortlet() throws IOException, PortalException {
_browser.get(_portlerURL.toExternalForm());
final String bodyText = _browser.getPageSource();
Assert.assertTrue(
"The portlet is not well deployed",
bodyText.contains("Sample Portlet is working!"));
}
This test class uses the following fields:
-
_browser
: Arquillian annotation@Drone
sets this field as a SeleniumWebDriver
(browser).@Drone private WebDriver _browser;
-
_portlerURL
: Liferay Arquillian annotation@PortalURL
assigns the portlet’s URL to this field.@PortalURL("arquillian_sample_portlet") private URL _portlerURL;
-
_firstParamter
and_secondParameter
: JavaScript selectors and Selenium annotation@FindBy
map these fields to the form’s inputs.@FindBy(css = "input[id$='firstParameter']") private WebElement _firstParamter; @FindBy(css = "input[id$='secondParameter']") private WebElement _secondParameter;
-
_add
: The form’ssubmit
button.@FindBy(css = "button[type=submit]") private WebElement _add;
-
_result
: Sum of the two numbers.@FindBy(css = "span[class='result']") private WebElement _result;
Using the *Parameter
fields, the testAdd
method injects numbers 2
and 3
into the form, submits the form, and asserts 5
as the result.
@Test
public void testAdd()
throws InterruptedException, IOException, PortalException {
_browser.get(_portlerURL.toExternalForm());
_firstParameter.clear();
_firstParameter.sendKeys("2");
_secondParameter.clear();
_secondParameter.sendKeys("3");
_add.click();
Thread.sleep(5000);
Assert.assertEquals("5", _result.getText());
}
Testing portlets via a web client is that simple!
Functional tests typically require more setup than integration tests.
Dependencies
In addition to the Liferay Arquillian container, JUnit, and an Arquillian JUnit
test container artifacts that the
integration test
required, the BasicPortletFunctionalTest
class requires Arquillian’s Graphine
extension to use the web client. Here’s the dependency from the Gradle file
build.gradle
:
testIntegrationCompile group: "org.jboss.arquillian.graphene", name: "graphene-webdriver", version: "2.1.0.Final"
The test requires additional Arquillian configuration elements too.
Arquillian Configuration
In addition to the deploymentExportPath
property introduced with the
integration test, this functional test specifies the browser type using
phantomjs
and the portal’s URL. Here’s the project’s arquillian.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<extension qualifier="webdriver">
<property name="browser">phantomjs</property>
</extension>
<extension qualifier="graphene">
<property name="url">http://localhost:8080</property>
</extension>
<engine>
<property name="deploymentExportPath">build/deployments</property>
</engine>
</arquillian>
You should use Portal properties to prevent Liferay DXP from launching a browser and the Setup Wizard.
Portal Properties
The Arquillian Blade Example specifies the following Portal properties in file
src/testIntegration/resources/portal-ext.properties
:
browser.launcher.url=
setup.wizard.enabled=false
Browsers other than the ones the functional tests launch can interfere with
tests. Setting browser.launcher.url
to an empty value prevents Liferay DXP from
launching a browser on its own. You don’t need Liferay DXP’s setup wizard either.
Setting setup.wizard.enabled=false
bypasses launching the Setup Wizard.
The example project’s Gradle task copyPortalExt
, copies the Portal properties
file into the Liferay DXP installation.
As you develop tests, you might want to track the parts of the product your tests cover. The example project uses JaCoCo to measure code coverage. JaCoCo is explained next.
If you’d rather launch the Arquillian Blade Example before investigating JaCoCo, skip to Running the Arquillian Example. Otherwise, jump into JaCoCo!