JSR286 portlet unit testing with Jetty, Pluto and JWebUnit

Posted by:

Background

Unit testing using JUnit has been around for quite some time, and even though I have used in early JUnit versions, it got really going when I also started to use maven. It was quite easy to create unit tests for any of my classes. Since I worked a lot with portlets I wanted to do expand my unit tests to include portlets.

 

The JSR286 portlet standard has been around for a while and it surprised me that I couldn't find any unit tests for JSR286 portlets, only JSR168 portlets. The most helpful blog I found was this one Testing Portlets with Jetty, Pluto and JWebUnit but again it applies to JSR168 portlets. Starting from there I tried to do the same thing with JSR286 portlets, but the pluto version used wasn't designed to work with JSR286 portlet. However, it gave me a starting point to recreate the setup for JSR286 portlet unit testing.

 

Setup

I will be using only a minimalistic portlet to demonstrate the setup. My starting point is a minimalistic portlet set up with maven. I demonstrated how to set that up in my last blog entry. From there I enhanced it a bit to allow for more testing. Here is the portlet class, the portlet.xml and the jsp I am using.

package com.mycompany.myportlet;

import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class MyPortlet extends GenericPortlet {

	public void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(
				"/view.jsp");
		rd.include(request, response);
	}
}

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" id="MyPortlet">
	<portlet>
		<portlet-name>MyPortlet</portlet-name>
		<display-name xml:lang="en">MyPortlet</display-name>
		<display-name>MyPortlet</display-name>
		<portlet-class>com.mycompany.myportlet.MyPortlet</portlet-class>
		<expiration-cache>0</expiration-cache>
		<supports>
			<mime-type>text/html</mime-type>
			<portlet-mode>view</portlet-mode>
		</supports>
		<supported-locale>en</supported-locale>
		<resource-bundle>com.mycompany.myportlet.nl.MyPortletResource</resource-bundle>
		<portlet-info>
			<title>MyPortlet</title>
			<short-title>MyPortlet</short-title>
			<keywords>MyPortlet</keywords>
		</portlet-info>
	</portlet>
	<default-namespace>http://MyPortlet/</default-namespace>
</portlet-app>

<%@page session="false" contentType="text/html" pageEncoding="ISO-8859-1" %>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>                
<portlet:defineObjects/>

<div id="HelloWorldDiv">
Hello World!
</div>

Maven dependencies

Next I set up the maven configuration. For this example I am using a simple pom.

<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">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mycompany.myportlet</groupId>
	<artifactId>MyPortlet</artifactId>
	<version>0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>My Portlet</name>
	<description>A basic portlet mavenized</description>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.17</version>
				<configuration>
					<useSystemClassLoader>false</useSystemClassLoader>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>javax.portlet</groupId>
			<artifactId>portlet-api</artifactId>
			<version>2.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.mortbay.jetty</groupId>
			<artifactId>jetty-maven-plugin</artifactId>
			<version>7.0.0.pre5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>net.sf.portletunit</groupId>
			<artifactId>maven-jetty-pluto-embedded</artifactId>
			<version>2.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>net.sourceforge.jwebunit</groupId>
			<artifactId>jwebunit-htmlunit-plugin</artifactId>
			<version>3.0</version>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>ch.qos.logback</groupId>
					<artifactId>logback-classic</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.7</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

I will describe what all of this does starting at the top. It starts with the basic maven configuration for the project. Moving on to the build section. There are some issues with loading classpaths and if I don't tell the surefire plugin to not use the system classloader, jetty will not be able to find the JSTL tag library and thus the entire test will fail.

 In the dependencies section I will start with the standard portlet api. This is required to compile the portlet class. Next is the jetty-maven-plugin. This is the lightweight web container used as a web server if you will when we are running the tests. This is then used together with the maven-jetty-pluto-embedded plugin. Pluto provides the portlet engine for the portlet  and maven-jetty-pluto-embedded provides the possibility to use pluto together with jetty. Starting with version 2.0 of this plugin the JSR286 portlet specification was supported. Next is jwebunit-htmlunit-plugin. This is what I use for the actual tests in my test class. The exclusion in the dependency is for us to be using my own custom logging configuration and so is slf4j-log4j12. Also note that all of these dependencies (except for the portlet api) are using the test scope. This is because I will only require these during the test phase.

Also as a sidenote. Some of these implementations have newer versions available in particular the jetty-maven-plugin, but since one of the critical components is maven-jetty-pluto-embedded, the version I use here is the latest compatible version I have found I can use.

The test class

Now off to the test class. Basically I do this in three steps. Setting up the server, running my tests and shutting down the server.

package mytest;

import static net.sourceforge.jwebunit.junit.JWebUnit.assertElementPresent;
import static net.sourceforge.jwebunit.junit.JWebUnit.assertTextPresent;
import static net.sourceforge.jwebunit.junit.JWebUnit.beginAt;
import static net.sourceforge.jwebunit.junit.JWebUnit.getTestContext;

import org.apache.pluto.container.driver.PortletServlet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.webapp.WebAppContext;

public class MyPortletTest {
	private Server server;
	private static final int SERVER_PORT = 8080;

	@Before
	public void setUp() throws Exception {
		System.setProperty("org.apache.pluto.embedded.portletIds", "MyPortlet");
		server = new Server(SERVER_PORT);
		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", "MyPortlet");
		portletServlet.setInitOrder(1);
		webapp.addServlet(portletServlet, "/PlutoInvoker/MyPortlet");
		server.addHandler(webapp);
		server.start();
		getTestContext().setBaseUrl("http://localhost:" + SERVER_PORT + "/test");
	}

	@After
	public void tearDown() throws Exception {
		server.stop();
	}

	@Test
	public void testDoView() {
		beginAt("/portal/index.jsp");
		assertTextPresent("Hello World!");
		assertElementPresent("HelloWorldDiv");
	}
}

The startup of the server can be done also through configuration, but I extended my work starting from the blog where I found the JSR168 examples so I will continue the same way. In the setUp() method I do all of the server configuration and startup work. The exact name of the method id not important, but it must use the @Before annotation. The first line defines the portlets I use in my tests, the exact name is not important but it must match the portlet name in the portlet.xml. There can be multiple portlets as long as they are on a comma-separated list. The server is created with only a port where the server is started. The port can pretty much be any port as long as it is not already in use.

 

Next the web application context is set up. Here I define a servlet url mapping and add pluto descriptors and classes in order to get a proper portlet engine. The web application context is added to the server and the server is started. Finally the base url to use for all tests is defined.

 

The tearDown method is run after all the tests have completed (using the @After annotation). All this method does is to stop the server.

 

Finally I have the test method. This is just a simple demonstration to get started. What you want to do with your tests is up to you. Any test method can contain a number of different tests and there can be multiple tests in a test class. Here I start navigating to the pluto portals front page, making sure a certain text and div is present.

 

Reduce logging

If you run the test with only the above setup, you will find there is some extensive logging done. This is because the logging is set to debug level. I find this a bit too talky myself. In order to fix this, first we must exclude the default logging from JWebUnit. Jetty uses slf4j and all I did from there was to add the log4j impementation of slf4j using this configuration.

log4j.rootLogger=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %c{1}:%L - %m%n

This configuration will set the logging to warn level. This will reduce the logging drastically but the cause will still be logged if an error fails. You may need to adjust the log level for further debugging if a test fails, but I found this to be a nice setting. I have also added a configuration which will output maven-like logging.

References

0

Add a Comment