After my last
two entries, I've gotten some questions about using pluto embedded in jetty to create automated integration tests for
JSR 168 portlets. Using the
maven-jetty-plugin for running the portlets is great for fast, iterative development. But it can't be used to run automated integration tests. Remembering an excellent
article from
Johannes Brodwall's blog about integration testing with
Jetty and
JWebUnit, I wanted to extend his approach to use the embedded jetty-pluto setup I have created. This turned out to be to be quite easy.
To illustrate the solution, let's start from scratch, creating a simple portlet displaying a personalized "Hello World" message (how original...).
Note that this is only a very simple example, outlining how it is possible to set up the infrastructure for simple web integration testing of portlets.The first step is to create a blank portlet project, using Maven 2 and the portlet archetype:
mvn archetype:create
-DgroupId=com.mycompany.app
-DartifactId=my-portlet
-DarchetypeArtifactId=maven-archetype-portlet
(Type this on one line)
I'll use Eclipse for this example, so I'll set up an Eclipse project for the portlet by typing
mvn eclipse:eclipse. Now you can go ahead importing the project into Eclipse as an existing project.
Unfortunately, it appears that the portlet arhcetype contains a
bug, so make sure you update the
portlet-class element in the
portlet.xml file with the correct portlet class:
<portlet-class>com.mycompany.app.MyPortlet</portlet-class>
Then we need some dependencies to
Jetty,
JWebUnit and the
maven-jetty-pluto-embedded artifact, so add these to the dependencies section of the
pom.xml file of the portlet project:
Update: The maven-jetty-pluto-embedded artifact is available in the Maven 2 repositories.
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-2.1</artifactId>
<version>6.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.jwebunit</groupId>
<artifactId>jwebunit-htmlunit-plugin</artifactId>
<version>1.4.1</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.bekk.boss</groupId>
<artifactId>maven-jetty-pluto-embedded</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
(The JWebUnit dependency has an indirect dependency on commons-logging, which for some weird reason
has a dependency on the servlet-api!? This version of the servlet-api causes problems for the version of jetty we're running, so we have to exclude it)
Run
mvn eclipse:eclipse again to download the new dependencies. If you want to know what the
maven-jetty-pluto-embedded artifact is, you should read my
previous entries).
Open the
MyPortlet portlet that was generated for us, and remove everything but the method signature for the
doView() method, like this:
public class MyPortlet extends GenericPortlet {
public void doView( RenderRequest request, RenderResponse response ) {
}
}
Now we'll start preparing the test, and this is where the fun begins. We'll have to configure Jetty in our test to use the embedded jetty pluto setup from the previous articles. Create a new source folder,
src/test/java, and create a new blank
WebTestCase:
package com.mycompany.app;
import net.sourceforge.jwebunit.junit.WebTestCase;
public class MyPortletIntegrationTest extends WebTestCase {
}
We'll have to create a
setUp() metohd that initializes the Jetty server, and configures it with the pluto driver:
protected Server server;
public void setUp() throws Exception {
System.setProperty("org.apache.pluto.embedded.portletId", "my-portlet");
server = new Server(8080);
WebAppContext webapp = new WebAppContext("src/main/webapp", "/test");
webapp.setDefaultsDescriptor("/WEB-INF/jetty-pluto-web-default.xml");
ServletHolder portletServlet = new ServletHolder(new PortletServlet());
portletServlet.setInitParameter("portlet-name", "my-portlet");
portletServlet.setInitOrder(1);
webapp.addServlet(portletServlet, "/PlutoInvoker/my-portlet");
server.addHandler(webapp);
server.start();
getTestContext().setBaseUrl("http://localhost:8080/test");
}
public void tearDown() throws Exception {
server.stop();
}
So what does all this mean? Basically, we have just copied the setup from the configuration of the maven-jetty-plugin that we used earlier, and configured the Jetty server programatically in our integration test. In addition, we've added and configured an instance of the pluto
PortletServlet so we don't have to modify our
web.xml file (which normally would be something our build takes care of). We also added a
tearDown() method to gracefully shut down the server.
Now it's time to write an actual test. We'll start very, very simple, by just testing if the portlet displays a simple message when it is displayed normally in the view mode:
public void testIndexPageDisplaysMessage() throws Exception {
// Start at the pluto driver entry point
beginAt("/pluto/index.jsp");
assertTextPresent("Hello World from MyPortlet!");
}
Running the test, it will of course fail since we have not implemented anything in the portlet yet. So let's create an
index.jsp file in the
/WEB-INF folder of the portlet:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
Hello World from MyPortlet!
In the
doView() method of the portlet, we'll just dispatch to this jsp:
public void doView( RenderRequest request, RenderResponse response ) throws IOException, PortletException {
getPortletContext().getRequestDispatcher("/WEB-INF/index.jsp").include(request, response);
}
Now, run the test again. This time, it should show a green bar! That was quick and simple! Let's try to complicate the case a little bit by adding and working with a form. The form should be a form with two text fields, one named
firstName and a second named
lastName. When submitting the form, the user should be displayed a personalized "Hello World" message:
public void testPersonalizedHelloWorldMessage() throws Exception {
// Start at the pluto driver entry point
beginAt("/pluto/index.jsp");
// Expect a form with name "helloWorldForm" present on the page
assertFormPresent("helloWorldForm");
// This form should have two text fields. We'll populate these with data
setWorkingForm("helloWorldForm");
setTextField("firstName", "Donald");
setTextField("lastName", "Duck");
submit();
assertTextPresent("Quack quack, Donald Duck!");
}
We populate the form with some data, submit it, and verify the result. Running the test now, you'll see it fails, as expected. So let's implement the expected functionality. Normally, I would use a MVC framework with portlet support, such as
Struts 2 or
Spring MVC, but since I'm only using the core APIs in this example, we'll have to use the portlet as a very basic controller. The portlet will handle the form submits and the logic for including the correct jsp. Let's start by adding the form to the
index.jsp page:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
Hello World from MyPortlet!
<form name="helloWorldForm" action="<portlet:actionURL/>" method="POST">
<b>First name</b>: <input type="text" name="firstName"/><br/>
<b>Last name</b>: <input type="text" name="lastName"/><br/>
<input type="submit"/>
</form>
Then we'll implement the
processAction() method and process the incoming form submit:
public void processAction(ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException, IOException {
String firstName = actionRequest.getParameter("firstName");
String lastName = actionRequest.getParameter("lastName");
actionResponse.setRenderParameter("firstName", firstName);
actionResponse.setRenderParameter("lastName", lastName);
actionResponse.setRenderParameter("action", "viewResult");
}
Then we'll add some "routing logic" to the
doView() method so it will dispatch us to the correct view:
public void doView( RenderRequest request, RenderResponse response ) throws IOException, PortletException {
String action = request.getParameter("action");
if("viewResult".equals(action)) {
getPortletContext().getRequestDispatcher("/WEB-INF/hello.jsp").include(request, response);
}
else {
getPortletContext().getRequestDispatcher("/WEB-INF/index.jsp").include(request, response);
}
}
And finally, the
/WEB-INF/hello.jsp file that generates the output:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
<portlet:defineObjects/>
Quack quack, <%=renderRequest.getParameter("firstName") %> <%=renderRequest.getParameter("lastName") %>!
Run the test again, and it should become green!
Next, let's assume that the portlet should do something useful in the edit portlet mode. But how can we simulate a click on the edit button? After some looking though the source of the generated pluto html, I found the XPath expressions that identifies each of the portlet mode and window state buttons. With this information, let's write a test that switches the portlet to edit mode, and assert that we get to the correct page:
public void testSwitchToEditMode() throws Exception {
beginAt("/pluto/index.jsp");
switchEdit();
assertTextPresent("This is MyPortlet in edit mode!");
}
private void switchEdit() {
clickElementByXPath("//span[@class='edit']/..");
}
The
switchEdit() method simulates a click on the edit mode control of the portlet, using the correct XPath expression.
Try to run the test, and it will turn red with a message stating that "Unable to locate element with XPath...". This is because pluto does not display the edit link for the portlet. Looking in the
portlet.xml for the portlet, you can see that currently the only supported portlet mode is
VIEW. So let's add
EDIT to the supported modes as well:
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
<portlet-mode>EDIT</portlet-mode>
</supports>
Running the test again, it will fail, but now with a different error message, stating that the "Expected text was not found". So it's time to implement the
doEdit() method and the corresponding JSP. Create an
edit.jsp file in the
/WEB-INF folder, which just contains
This is MyPortlet in edit mode!
In the portlet, the
doEdit() method should look like this:
public void doEdit(RenderRequest request, RenderResponse response) throws IOException, PortletException {
getPortletContext().getRequestDispatcher("/WEB-INF/edit.jsp").include(request, response);
}
This time when you run the test, it should turn green.
As you can see, writing integration tests for a portlet is pretty simple, using the right tools. But why this approach, instead of just running JWebUnit (or Selenium or <insert your favourite web testing framework here>) against a running portal server with the portlet installed? First of all, using this "in process" approach is quicker! It doesn't require you to start a big, heavy portal server, and no login is required. Asserting and verifying content is easier since the only portlet generating content is the one you're testing. And since it's in process, it's really simple to debug. Just run the JUnit test in debug mode, and you can debug the portlet as it responds to the request of the web testing framework.
As a final note, I have uploaded an abstract WebTestCase that you can
download and use as a base for your integration tests. It has everything set up for starting Jetty with the pluto portal driver, and with helper methods for switching portlet modes and window states.