You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2020/04/05 05:46:06 UTC

[struts] 01/06: Proposed implementation to address WW-5030. Introduce new junit-portlet plugin. - Utilizes a copy of org.springframework.mock.web.portlet package from Spring 4.3.x, refactored to org.apache.struts2.mock.web.portlet. Also copies one interface from org.apache.struts2.mock.web.portlet. The package-info.java files identify the package renaming. - Provides test cases for the mock objects. - Modifies the portlet plugin to use the junit-portlet plugin as a dependency. Adds a small boun [...]

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch action-context-boost
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 2e7a6b967e0b95576107073faa8c98caf7c2a428
Author: JCgH4164838Gh792C124B5 <43...@users.noreply.github.com>
AuthorDate: Sun Mar 22 13:31:54 2020 -0400

    Proposed implementation to address WW-5030.
    Introduce new junit-portlet plugin.
    - Utilizes a copy of org.springframework.mock.web.portlet package from
      Spring 4.3.x, refactored to org.apache.struts2.mock.web.portlet.  Also
      copies one interface from org.apache.struts2.mock.web.portlet.  The
      package-info.java files identify the package renaming.
    - Provides test cases for the mock objects.
    - Modifies the portlet plugin to use the junit-portlet plugin as a
      dependency.  Adds a small boundary case fix for PortletRequestMap.
---
 plugins/{portlet => junit-portlet}/pom.xml         |   96 +-
 .../mock/web/portlet/MockActionRequest.java        |   86 ++
 .../mock/web/portlet/MockActionResponse.java       |  132 ++
 .../struts2/mock/web/portlet/MockBaseURL.java      |  161 ++
 .../struts2/mock/web/portlet/MockCacheControl.java |   78 +
 .../mock/web/portlet/MockClientDataRequest.java    |  135 ++
 .../apache/struts2/mock/web/portlet/MockEvent.java |   91 ++
 .../struts2/mock/web/portlet/MockEventRequest.java |   90 ++
 .../mock/web/portlet/MockEventResponse.java        |   35 +
 .../struts2/mock/web/portlet/MockMimeResponse.java |  259 ++++
 .../web/portlet/MockMultipartActionRequest.java    |  101 ++
 .../mock/web/portlet/MockPortalContext.java        |  106 ++
 .../mock/web/portlet/MockPortletConfig.java        |  194 +++
 .../mock/web/portlet/MockPortletContext.java       |  285 ++++
 .../mock/web/portlet/MockPortletPreferences.java   |  123 ++
 .../mock/web/portlet/MockPortletRequest.java       |  565 +++++++
 .../web/portlet/MockPortletRequestDispatcher.java  |   87 ++
 .../mock/web/portlet/MockPortletResponse.java      |  202 +++
 .../mock/web/portlet/MockPortletSession.java       |  249 +++
 .../struts2/mock/web/portlet/MockPortletURL.java   |  121 ++
 .../mock/web/portlet/MockRenderRequest.java        |   94 ++
 .../mock/web/portlet/MockRenderResponse.java       |   90 ++
 .../mock/web/portlet/MockResourceRequest.java      |  132 ++
 .../mock/web/portlet/MockResourceResponse.java     |   41 +
 .../struts2/mock/web/portlet/MockResourceURL.java  |   73 +
 .../mock/web/portlet/MockStateAwareResponse.java   |  165 ++
 .../web/portlet/ServletWrappingPortletContext.java |  161 ++
 .../portlet/multipart/MultipartActionRequest.java  |   41 +
 .../mock/web/portlet/multipart/package-info.java   |   38 +
 .../struts2/mock/web/portlet/package-info.java     |   35 +
 plugins/junit-portlet/src/site/site.xml            |   56 +
 .../StrutsSpringPortletMockObjectsTest.java        | 1596 ++++++++++++++++++++
 .../src/test/resources/applicationContext.xml      |   36 +
 plugins/pom.xml                                    |    1 +
 plugins/portlet/pom.xml                            |    6 +
 .../org/apache/struts2/StrutsPortletTestCase.java  |   10 +-
 .../apache/struts2/portlet/PortletRequestMap.java  |   10 +-
 .../struts2/components/PortletUrlRendererTest.java |    6 +-
 .../struts2/portlet/PortletRequestMapTest.java     |    2 +-
 .../struts2/portlet/PortletSessionMapTest.java     |    2 +-
 .../portlet/dispatcher/Jsr168DispatcherTest.java   |    4 +-
 .../portlet/dispatcher/Jsr286DispatcherTest.java   |    4 +-
 pom.xml                                            |    5 +
 43 files changed, 5712 insertions(+), 92 deletions(-)

diff --git a/plugins/portlet/pom.xml b/plugins/junit-portlet/pom.xml
similarity index 57%
copy from plugins/portlet/pom.xml
copy to plugins/junit-portlet/pom.xml
index c5198b8..adfab00 100644
--- a/plugins/portlet/pom.xml
+++ b/plugins/junit-portlet/pom.xml
@@ -27,47 +27,44 @@
         <version>2.6-SNAPSHOT</version>
     </parent>
 
-    <artifactId>struts2-portlet-plugin</artifactId>
+    <artifactId>struts2-junit-portlet-plugin</artifactId>
     <packaging>jar</packaging>
-    <name>Struts 2 Portlet Plugin</name>
+    <name>Struts 2 JUnit Portlet Plugin</name>
 
     <dependencies>
-        <!-- junit and related JARs are needed for 'compile'! -->
-        <!-- Due to StrutsPortletTestCase shall be distributed to applications. -->
         <dependency>
-            <groupId>org.apache.struts</groupId>
-            <artifactId>struts2-junit-plugin</artifactId>
-            <optional>true</optional>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>org.apache.struts</groupId>
-            <artifactId>struts2-velocity-plugin</artifactId>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <optional>true</optional>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
         </dependency>
-
-        <!-- spring-core is required for javadoc of jdk8 -->
-        <!-- It is indirectly referenced by struts2-junit-plugin. -->
         <dependency>
             <groupId>org.springframework</groupId>
-            <artifactId>spring-core</artifactId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-junit-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-spring-plugin</artifactId>
             <optional>true</optional>
         </dependency>
-
         <dependency>
             <groupId>javax.servlet.jsp</groupId>
             <artifactId>jsp-api</artifactId>
             <scope>provided</scope>
         </dependency>
-
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
         </dependency>
 
         <!-- Portlet -->
@@ -77,59 +74,6 @@
             <scope>provided</scope>
         </dependency>
 
-        <dependency>
-            <groupId>mockobjects</groupId>
-            <artifactId>mockobjects-jdk1.3-j2ee1.3</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.easymock</groupId>
-            <artifactId>easymock</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>jmock</groupId>
-            <artifactId>jmock</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>jmock</groupId>
-            <artifactId>jmock-cglib</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>mockobjects</groupId>
-            <artifactId>mockobjects-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
-            <version>${log4j2.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <!-- Mocks for unit testing (by Spring) -->
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-test</artifactId>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-webmvc-portlet</artifactId>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <groupId>commons-fileupload</groupId>
-            <artifactId>commons-fileupload</artifactId>
-            <scope>test</scope>
-        </dependency>
-
         <!-- The Servlet API mocks in Spring Framework 4.x only supports Servlet 3.0 and higher.
            This is only necessary in tests-->
         <dependency>
@@ -140,6 +84,6 @@
         </dependency>
     </dependencies>
     <properties>
-    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 </project>
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionRequest.java
new file mode 100644
index 0000000..390df5c
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ActionRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockActionRequest extends MockClientDataRequest implements ActionRequest {
+
+	/**
+	 * Create a new MockActionRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @see org.springframework.mock.web.portlet.MockPortalContext
+	 * @see org.springframework.mock.web.portlet.MockPortletContext
+	 */
+	public MockActionRequest() {
+		super();
+	}
+
+	/**
+	 * Create a new MockActionRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param actionName the name of the action to trigger
+	 */
+	public MockActionRequest(String actionName) {
+		super();
+		setParameter(ActionRequest.ACTION_NAME, actionName);
+	}
+
+	/**
+	 * Create a new MockActionRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param portletMode the mode that the portlet runs in
+	 */
+	public MockActionRequest(PortletMode portletMode) {
+		super();
+		setPortletMode(portletMode);
+	}
+
+	/**
+	 * Create a new MockActionRequest with a default {@link MockPortalContext}.
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockActionRequest(PortletContext portletContext) {
+		super(portletContext);
+	}
+
+	/**
+	 * Create a new MockActionRequest.
+	 * @param portalContext the PortalContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockActionRequest(PortalContext portalContext, PortletContext portletContext) {
+		super(portalContext, portletContext);
+	}
+
+
+	@Override
+	protected String getLifecyclePhase() {
+		return ACTION_PHASE;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionResponse.java
new file mode 100644
index 0000000..b120ffd
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockActionResponse.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ActionResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockActionResponse extends MockStateAwareResponse implements ActionResponse {
+
+	private boolean redirectAllowed = true;
+
+	private String redirectedUrl;
+
+
+	/**
+	 * Create a new MockActionResponse with a default {@link MockPortalContext}.
+	 * @see MockPortalContext
+	 */
+	public MockActionResponse() {
+		super();
+	}
+
+	/**
+	 * Create a new MockActionResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 */
+	public MockActionResponse(PortalContext portalContext) {
+		super(portalContext);
+	}
+
+
+	@Override
+	public void setWindowState(WindowState windowState) throws WindowStateException {
+		if (this.redirectedUrl != null) {
+			throw new IllegalStateException("Cannot set WindowState after sendRedirect has been called");
+		}
+		super.setWindowState(windowState);
+		this.redirectAllowed = false;
+	}
+
+	@Override
+	public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+		if (this.redirectedUrl != null) {
+			throw new IllegalStateException("Cannot set PortletMode after sendRedirect has been called");
+		}
+		super.setPortletMode(portletMode);
+		this.redirectAllowed = false;
+	}
+
+	@Override
+	public void setRenderParameters(Map<String, String[]> parameters) {
+		if (this.redirectedUrl != null) {
+			throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+		}
+		super.setRenderParameters(parameters);
+		this.redirectAllowed = false;
+	}
+
+	@Override
+	public void setRenderParameter(String key, String value) {
+		if (this.redirectedUrl != null) {
+			throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+		}
+		super.setRenderParameter(key, value);
+		this.redirectAllowed = false;
+	}
+
+	@Override
+	public void setRenderParameter(String key, String[] values) {
+		if (this.redirectedUrl != null) {
+			throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+		}
+		super.setRenderParameter(key, values);
+		this.redirectAllowed = false;
+	}
+
+	@Override
+	public void sendRedirect(String location) throws IOException {
+		if (!this.redirectAllowed) {
+			throw new IllegalStateException(
+					"Cannot call sendRedirect after windowState, portletMode, or renderParameters have been set");
+		}
+		Assert.notNull(location, "Redirect URL must not be null");
+		this.redirectedUrl = location;
+	}
+
+	@Override
+	public void sendRedirect(String location, String renderUrlParamName) throws IOException {
+		sendRedirect(location);
+		if (renderUrlParamName != null) {
+                    // NOTE: Changed since original Spring 4.3.x version.
+                    // Cannot call setRenderParameter(renderUrlParamName, location) here as it always results in an IllegalStateException - since a redirect has been called.
+                    // Due to this, we call super.setRenderParameter() directly, otherwise this method can never successfully be called.
+			super.setRenderParameter(renderUrlParamName, location);
+		}
+	}
+
+	public String getRedirectedUrl() {
+		return this.redirectedUrl;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockBaseURL.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockBaseURL.java
new file mode 100644
index 0000000..c0a1995
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockBaseURL.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.BaseURL;
+import javax.portlet.PortletSecurityException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.BaseURL} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public abstract class MockBaseURL implements BaseURL {
+
+	public static final String URL_TYPE_RENDER = "render";
+
+	public static final String URL_TYPE_ACTION = "action";
+
+	private static final String ENCODING = "UTF-8";
+
+
+	protected final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
+
+	private boolean secure = false;
+
+	private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
+
+
+	//---------------------------------------------------------------------
+	// BaseURL methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void setParameter(String key, String value) {
+		Assert.notNull(key, "Parameter key must be null");
+		Assert.notNull(value, "Parameter value must not be null");
+		this.parameters.put(key, new String[] {value});
+	}
+
+	@Override
+	public void setParameter(String key, String[] values) {
+		Assert.notNull(key, "Parameter key must be null");
+		Assert.notNull(values, "Parameter values must not be null");
+		this.parameters.put(key, values);
+	}
+
+	@Override
+	public void setParameters(Map<String, String[]> parameters) {
+		Assert.notNull(parameters, "Parameters Map must not be null");
+		this.parameters.clear();
+		this.parameters.putAll(parameters);
+	}
+
+	public Set<String> getParameterNames() {
+		return this.parameters.keySet();
+	}
+
+	public String getParameter(String name) {
+		String[] arr = this.parameters.get(name);
+		return (arr != null && arr.length > 0 ? arr[0] : null);
+	}
+
+	public String[] getParameterValues(String name) {
+		return this.parameters.get(name);
+	}
+
+	@Override
+	public Map<String, String[]> getParameterMap() {
+		return Collections.unmodifiableMap(this.parameters);
+	}
+
+	@Override
+	public void setSecure(boolean secure) throws PortletSecurityException {
+		this.secure = secure;
+	}
+
+	public boolean isSecure() {
+		return this.secure;
+	}
+
+	@Override
+	public void write(Writer out) throws IOException {
+		out.write(toString());
+	}
+
+	@Override
+	public void write(Writer out, boolean escapeXML) throws IOException {
+		out.write(toString());
+	}
+
+	@Override
+	public void addProperty(String key, String value) {
+		String[] values = this.properties.get(key);
+		if (values != null) {
+			this.properties.put(key, StringUtils.addStringToArray(values, value));
+		}
+		else {
+			this.properties.put(key, new String[] {value});
+		}
+	}
+
+	@Override
+	public void setProperty(String key, String value) {
+		this.properties.put(key, new String[] {value});
+	}
+
+	public Map<String, String[]> getProperties() {
+		return Collections.unmodifiableMap(this.properties);
+	}
+
+
+	protected String encodeParameter(String name, String value) {
+		try {
+			return URLEncoder.encode(name, ENCODING) + "=" + URLEncoder.encode(value, ENCODING);
+		}
+		catch (UnsupportedEncodingException ex) {
+			return null;
+		}
+	}
+
+	protected String encodeParameter(String name, String[] values) {
+		try {
+			StringBuilder sb = new StringBuilder();
+			for (int i = 0, n = values.length; i < n; i++) {
+				sb.append(i > 0 ? ";" : "").append(URLEncoder.encode(name, ENCODING)).append("=")
+						.append(URLEncoder.encode(values[i], ENCODING));
+			}
+			return sb.toString();
+		}
+		catch (UnsupportedEncodingException ex) {
+			return null;
+		}
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockCacheControl.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockCacheControl.java
new file mode 100644
index 0000000..f0ae50a
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockCacheControl.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.CacheControl;
+
+/**
+ * Mock implementation of the {@link javax.portlet.CacheControl} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockCacheControl implements CacheControl {
+
+	private int expirationTime = 0;
+
+	private boolean publicScope = false;
+
+	private String etag;
+
+	private boolean useCachedContent = false;
+
+
+	@Override
+	public int getExpirationTime() {
+		return this.expirationTime;
+	}
+
+	@Override
+	public void setExpirationTime(int time) {
+		this.expirationTime = time;
+	}
+
+	@Override
+	public boolean isPublicScope() {
+		return this.publicScope;
+	}
+
+	@Override
+	public void setPublicScope(boolean publicScope) {
+		this.publicScope = publicScope;
+	}
+
+	@Override
+	public String getETag() {
+		return this.etag;
+	}
+
+	@Override
+	public void setETag(String token) {
+		this.etag = token;
+	}
+
+	@Override
+	public boolean useCachedContent() {
+		return this.useCachedContent;
+	}
+
+	@Override
+	public void setUseCachedContent(boolean useCachedContent) {
+		this.useCachedContent = useCachedContent;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockClientDataRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockClientDataRequest.java
new file mode 100644
index 0000000..44792d1
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockClientDataRequest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import javax.portlet.ClientDataRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ClientDataRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockClientDataRequest extends MockPortletRequest implements ClientDataRequest {
+
+	private String characterEncoding;
+
+	private byte[] content;
+
+	private String contentType;
+
+	private String method;
+
+
+	/**
+	 * Create a new MockClientDataRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @see org.springframework.mock.web.portlet.MockPortalContext
+	 * @see org.springframework.mock.web.portlet.MockPortletContext
+	 */
+	public MockClientDataRequest() {
+		super();
+	}
+
+	/**
+	 * Create a new MockClientDataRequest with a default {@link MockPortalContext}.
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockClientDataRequest(PortletContext portletContext) {
+		super(portletContext);
+	}
+
+	/**
+	 * Create a new MockClientDataRequest.
+	 * @param portalContext the PortalContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockClientDataRequest(PortalContext portalContext, PortletContext portletContext) {
+		super(portalContext, portletContext);
+	}
+
+
+	public void setContent(byte[] content) {
+		this.content = content;
+	}
+
+	@Override
+	public InputStream getPortletInputStream() throws IOException {
+		if (this.content != null) {
+			return new ByteArrayInputStream(this.content);
+		}
+		else {
+			return null;
+		}
+	}
+
+	@Override
+	public void setCharacterEncoding(String characterEncoding) {
+		this.characterEncoding = characterEncoding;
+	}
+
+	@Override
+	public BufferedReader getReader() throws UnsupportedEncodingException {
+		if (this.content != null) {
+			InputStream sourceStream = new ByteArrayInputStream(this.content);
+			Reader sourceReader = (this.characterEncoding != null) ?
+				new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream);
+			return new BufferedReader(sourceReader);
+		}
+		else {
+			return null;
+		}
+	}
+
+	@Override
+	public String getCharacterEncoding() {
+		return this.characterEncoding;
+	}
+
+	public void setContentType(String contentType) {
+		this.contentType = contentType;
+	}
+
+	@Override
+	public String getContentType() {
+		return this.contentType;
+	}
+
+	@Override
+	public int getContentLength() {
+		return (this.content != null ? content.length : -1);
+	}
+
+	public void setMethod(String method) {
+		this.method = method;
+	}
+
+	@Override
+	public String getMethod() {
+		return this.method;
+	}
+
+}
\ No newline at end of file
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEvent.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEvent.java
new file mode 100644
index 0000000..2e9a411
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEvent.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.Serializable;
+import javax.portlet.Event;
+import javax.xml.namespace.QName;
+
+/**
+ * Mock implementation of the {@link javax.portlet.Event} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see MockEventRequest
+ */
+public class MockEvent implements Event {
+
+	private final QName name;
+
+	private final Serializable value;
+
+
+	/**
+	 * Create a new MockEvent with the given name.
+	 * @param name the name of the event
+	 */
+	public MockEvent(QName name) {
+		this.name = name;
+		this.value = null;
+	}
+
+	/**
+	 * Create a new MockEvent with the given name and value.
+	 * @param name the name of the event
+	 * @param value the associated payload of the event
+	 */
+	public MockEvent(QName name, Serializable value) {
+		this.name = name;
+		this.value = value;
+	}
+
+	/**
+	 * Create a new MockEvent with the given name.
+	 * @param name the name of the event
+	 */
+	public MockEvent(String name) {
+		this.name = new QName(name);
+		this.value = null;
+	}
+
+	/**
+	 * Create a new MockEvent with the given name and value.
+	 * @param name the name of the event
+	 * @param value the associated payload of the event
+	 */
+	public MockEvent(String name, Serializable value) {
+		this.name = new QName(name);
+		this.value = value;
+	}
+
+
+	@Override
+	public QName getQName() {
+		return this.name;
+	}
+
+	@Override
+	public String getName() {
+		return this.name.getLocalPart();
+	}
+
+	@Override
+	public Serializable getValue() {
+		return this.value;
+	}
+
+}
\ No newline at end of file
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventRequest.java
new file mode 100644
index 0000000..c055511
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventRequest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.Event;
+import javax.portlet.EventRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+
+/**
+ * Mock implementation of the {@link javax.portlet.EventRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockEventRequest extends MockPortletRequest implements EventRequest {
+
+	private final Event event;
+
+	private String method;
+
+
+	/**
+	 * Create a new MockEventRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param event the event that this request wraps
+	 * @see MockEvent
+	 */
+	public MockEventRequest(Event event) {
+		super();
+		this.event = event;
+	}
+
+	/**
+	 * Create a new MockEventRequest with a default {@link MockPortalContext}.
+	 * @param event the event that this request wraps
+	 * @param portletContext the PortletContext that the request runs in
+	 * @see MockEvent
+	 */
+	public MockEventRequest(Event event, PortletContext portletContext) {
+		super(portletContext);
+		this.event = event;
+	}
+
+	/**
+	 * Create a new MockEventRequest.
+	 * @param event the event that this request wraps
+	 * @param portalContext the PortletContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockEventRequest(Event event, PortalContext portalContext, PortletContext portletContext) {
+		super(portalContext, portletContext);
+		this.event = event;
+	}
+
+
+	@Override
+	protected String getLifecyclePhase() {
+		return EVENT_PHASE;
+	}
+
+	@Override
+	public Event getEvent() {
+		return this.event;
+	}
+
+	public void setMethod(String method) {
+		this.method = method;
+	}
+
+	@Override
+	public String getMethod() {
+		return this.method;
+	}
+
+}
\ No newline at end of file
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventResponse.java
new file mode 100644
index 0000000..9bda32e
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockEventResponse.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.EventRequest;
+import javax.portlet.EventResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.EventResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockEventResponse extends MockStateAwareResponse implements EventResponse {
+
+	@Override
+	public void setRenderParameters(EventRequest request) {
+		setRenderParameters(request.getParameterMap());
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMimeResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMimeResponse.java
new file mode 100644
index 0000000..cb654cb
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMimeResponse.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.portlet.CacheControl;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletURL;
+import javax.portlet.ResourceURL;
+
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.MimeResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockMimeResponse extends MockPortletResponse implements MimeResponse {
+
+	private PortletRequest request;
+
+	private String contentType;
+
+	private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
+
+	private PrintWriter writer;
+
+	private Locale locale = Locale.getDefault();
+
+	private int bufferSize = 4096;
+
+	private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+	private final CacheControl cacheControl = new MockCacheControl();
+
+	private boolean committed;
+
+	private String includedUrl;
+
+	private String forwardedUrl;
+
+
+	/**
+	 * Create a new MockMimeResponse with a default {@link MockPortalContext}.
+	 * @see org.springframework.mock.web.portlet.MockPortalContext
+	 */
+	public MockMimeResponse() {
+		super();
+	}
+
+	/**
+	 * Create a new MockMimeResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 */
+	public MockMimeResponse(PortalContext portalContext) {
+		super(portalContext);
+	}
+
+	/**
+	 * Create a new MockMimeResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 * @param request the corresponding render/resource request that this response
+	 * is being generated for
+	 */
+	public MockMimeResponse(PortalContext portalContext, PortletRequest request) {
+		super(portalContext);
+		this.request = request;
+	}
+
+
+	//---------------------------------------------------------------------
+	// RenderResponse methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void setContentType(String contentType) {
+		if (this.request != null) {
+			Enumeration<String> supportedTypes = this.request.getResponseContentTypes();
+			if (!CollectionUtils.contains(supportedTypes, contentType)) {
+				throw new IllegalArgumentException("Content type [" + contentType + "] not in supported list: " +
+						Collections.list(supportedTypes));
+			}
+		}
+		this.contentType = contentType;
+	}
+
+	@Override
+	public String getContentType() {
+		return this.contentType;
+	}
+
+	public void setCharacterEncoding(String characterEncoding) {
+		this.characterEncoding = characterEncoding;
+	}
+
+	@Override
+	public String getCharacterEncoding() {
+		return this.characterEncoding;
+	}
+
+	@Override
+	public PrintWriter getWriter() throws UnsupportedEncodingException {
+		if (this.writer == null) {
+			Writer targetWriter = (this.characterEncoding != null
+					? new OutputStreamWriter(this.outputStream, this.characterEncoding)
+					: new OutputStreamWriter(this.outputStream));
+			this.writer = new PrintWriter(targetWriter);
+		}
+		return this.writer;
+	}
+
+	public byte[] getContentAsByteArray() {
+		flushBuffer();
+		return this.outputStream.toByteArray();
+	}
+
+	public String getContentAsString() throws UnsupportedEncodingException {
+		flushBuffer();
+		return (this.characterEncoding != null)
+				? this.outputStream.toString(this.characterEncoding)
+				: this.outputStream.toString();
+	}
+
+	public void setLocale(Locale locale) {
+		this.locale = locale;
+	}
+
+	@Override
+	public Locale getLocale() {
+		return this.locale;
+	}
+
+	@Override
+	public void setBufferSize(int bufferSize) {
+		this.bufferSize = bufferSize;
+	}
+
+	@Override
+	public int getBufferSize() {
+		return this.bufferSize;
+	}
+
+	@Override
+	public void flushBuffer() {
+		if (this.writer != null) {
+			this.writer.flush();
+		}
+		if (this.outputStream != null) {
+			try {
+				this.outputStream.flush();
+			}
+			catch (IOException ex) {
+				throw new IllegalStateException("Could not flush OutputStream: " + ex.getMessage());
+			}
+		}
+		this.committed = true;
+	}
+
+	@Override
+	public void resetBuffer() {
+		if (this.committed) {
+			throw new IllegalStateException("Cannot reset buffer - response is already committed");
+		}
+		this.outputStream.reset();
+	}
+
+	public void setCommitted(boolean committed) {
+		this.committed = committed;
+	}
+
+	@Override
+	public boolean isCommitted() {
+		return this.committed;
+	}
+
+	@Override
+	public void reset() {
+		resetBuffer();
+		this.characterEncoding = null;
+		this.contentType = null;
+		this.locale = null;
+	}
+
+	@Override
+	public OutputStream getPortletOutputStream() throws IOException {
+		return this.outputStream;
+	}
+
+	@Override
+	public PortletURL createRenderURL() {
+		return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_RENDER);
+	}
+
+	@Override
+	public PortletURL createActionURL() {
+		return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_ACTION);
+	}
+
+	@Override
+	public ResourceURL createResourceURL() {
+		return new MockResourceURL();
+	}
+
+	@Override
+	public CacheControl getCacheControl() {
+		return this.cacheControl;
+	}
+
+
+	//---------------------------------------------------------------------
+	// Methods for MockPortletRequestDispatcher
+	//---------------------------------------------------------------------
+
+	public void setIncludedUrl(String includedUrl) {
+		this.includedUrl = includedUrl;
+	}
+
+	public String getIncludedUrl() {
+		return this.includedUrl;
+	}
+
+	public void setForwardedUrl(String forwardedUrl) {
+		this.forwardedUrl = forwardedUrl;
+	}
+
+	public String getForwardedUrl() {
+		return this.forwardedUrl;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMultipartActionRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMultipartActionRequest.java
new file mode 100644
index 0000000..396e8cf
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockMultipartActionRequest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.apache.struts2.mock.web.portlet.multipart.MultipartActionRequest;
+
+/**
+ * Mock implementation of the
+ * {@link org.springframework.web.portlet.multipart.MultipartActionRequest} interface.
+ *
+ * <p>Useful for testing application controllers that access multipart uploads.
+ * The {@link org.springframework.mock.web.MockMultipartFile} can be used to
+ * populate these mock requests with files.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @since 2.0
+ * @see org.springframework.mock.web.MockMultipartFile
+ */
+public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest {
+
+	private final MultiValueMap<String, MultipartFile> multipartFiles =
+			new LinkedMultiValueMap<String, MultipartFile>();
+
+
+	/**
+	 * Add a file to this request. The parameter name from the multipart
+	 * form is taken from the {@link org.springframework.web.multipart.MultipartFile#getName()}.
+	 * @param file multipart file to be added
+	 */
+	public void addFile(MultipartFile file) {
+		Assert.notNull(file, "MultipartFile must not be null");
+		this.multipartFiles.add(file.getName(), file);
+	}
+
+	@Override
+	public Iterator<String> getFileNames() {
+		return this.multipartFiles.keySet().iterator();
+	}
+
+	@Override
+	public MultipartFile getFile(String name) {
+		return this.multipartFiles.getFirst(name);
+	}
+
+	@Override
+	public List<MultipartFile> getFiles(String name) {
+		List<MultipartFile> multipartFiles = this.multipartFiles.get(name);
+		if (multipartFiles != null) {
+			return multipartFiles;
+		}
+		else {
+			return Collections.emptyList();
+		}
+	}
+
+	@Override
+	public Map<String, MultipartFile> getFileMap() {
+		return this.multipartFiles.toSingleValueMap();
+	}
+
+	@Override
+	public MultiValueMap<String, MultipartFile> getMultiFileMap() {
+		return new LinkedMultiValueMap<String, MultipartFile>(this.multipartFiles);
+	}
+
+	@Override
+	public String getMultipartContentType(String paramOrFileName) {
+		MultipartFile file = getFile(paramOrFileName);
+		if (file != null) {
+			return file.getContentType();
+		}
+		else {
+			return null;
+		}
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortalContext.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortalContext.java
new file mode 100644
index 0000000..0c7eccf
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortalContext.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.WindowState;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortalContext} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortalContext implements PortalContext {
+
+	private final Map<String, String> properties = new HashMap<String, String>();
+
+	private final List<PortletMode> portletModes;
+
+	private final List<WindowState> windowStates;
+
+
+	/**
+	 * Create a new MockPortalContext
+	 * with default PortletModes (VIEW, EDIT, HELP)
+	 * and default WindowStates (NORMAL, MAXIMIZED, MINIMIZED).
+	 * @see javax.portlet.PortletMode
+	 * @see javax.portlet.WindowState
+	 */
+	public MockPortalContext() {
+		this.portletModes = new ArrayList<PortletMode>(3);
+		this.portletModes.add(PortletMode.VIEW);
+		this.portletModes.add(PortletMode.EDIT);
+		this.portletModes.add(PortletMode.HELP);
+
+		this.windowStates = new ArrayList<WindowState>(3);
+		this.windowStates.add(WindowState.NORMAL);
+		this.windowStates.add(WindowState.MAXIMIZED);
+		this.windowStates.add(WindowState.MINIMIZED);
+	}
+
+	/**
+	 * Create a new MockPortalContext with the given PortletModes and WindowStates.
+	 * @param supportedPortletModes the List of supported PortletMode instances
+	 * @param supportedWindowStates the List of supported WindowState instances
+	 * @see javax.portlet.PortletMode
+	 * @see javax.portlet.WindowState
+	 */
+	public MockPortalContext(List<PortletMode> supportedPortletModes, List<WindowState> supportedWindowStates) {
+		this.portletModes = new ArrayList<PortletMode>(supportedPortletModes);
+		this.windowStates = new ArrayList<WindowState>(supportedWindowStates);
+	}
+
+
+	@Override
+	public String getPortalInfo() {
+		return "MockPortal/1.0";
+	}
+
+	public void setProperty(String name, String value) {
+		this.properties.put(name, value);
+	}
+
+	@Override
+	public String getProperty(String name) {
+		return this.properties.get(name);
+	}
+
+	@Override
+	public Enumeration<String> getPropertyNames() {
+		return Collections.enumeration(this.properties.keySet());
+	}
+
+	@Override
+	public Enumeration<PortletMode> getSupportedPortletModes() {
+		return Collections.enumeration(this.portletModes);
+	}
+
+	@Override
+	public Enumeration<WindowState> getSupportedWindowStates() {
+		return Collections.enumeration(this.windowStates);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletConfig.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletConfig.java
new file mode 100644
index 0000000..33d7e90
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletConfig.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletConfig} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletConfig implements PortletConfig {
+
+	private final PortletContext portletContext;
+
+	private final String portletName;
+
+	private final Map<Locale, ResourceBundle> resourceBundles = new HashMap<Locale, ResourceBundle>();
+
+	private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+	private final Set<String> publicRenderParameterNames = new LinkedHashSet<String>();
+
+	private String defaultNamespace = XMLConstants.NULL_NS_URI;
+
+	private final Set<QName> publishingEventQNames = new LinkedHashSet<QName>();
+
+	private final Set<QName> processingEventQNames = new LinkedHashSet<QName>();
+
+	private final Set<Locale> supportedLocales = new LinkedHashSet<Locale>();
+
+	private final Map<String, String[]> containerRuntimeOptions = new LinkedHashMap<String, String[]>();
+
+
+	/**
+	 * Create a new MockPortletConfig with a default {@link MockPortletContext}.
+	 */
+	public MockPortletConfig() {
+		this(null, "");
+	}
+
+	/**
+	 * Create a new MockPortletConfig with a default {@link MockPortletContext}.
+	 * @param portletName the name of the portlet
+	 */
+	public MockPortletConfig(String portletName) {
+		this(null, portletName);
+	}
+
+	/**
+	 * Create a new MockPortletConfig.
+	 * @param portletContext the PortletContext that the portlet runs in
+	 */
+	public MockPortletConfig(PortletContext portletContext) {
+		this(portletContext, "");
+	}
+
+	/**
+	 * Create a new MockPortletConfig.
+	 * @param portletContext the PortletContext that the portlet runs in
+	 * @param portletName the name of the portlet
+	 */
+	public MockPortletConfig(PortletContext portletContext, String portletName) {
+		this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+		this.portletName = portletName;
+	}
+
+
+	@Override
+	public String getPortletName() {
+		return this.portletName;
+	}
+
+	@Override
+	public PortletContext getPortletContext() {
+		return this.portletContext;
+	}
+
+	public void setResourceBundle(Locale locale, ResourceBundle resourceBundle) {
+		Assert.notNull(locale, "Locale must not be null");
+		this.resourceBundles.put(locale, resourceBundle);
+	}
+
+	@Override
+	public ResourceBundle getResourceBundle(Locale locale) {
+		Assert.notNull(locale, "Locale must not be null");
+		return this.resourceBundles.get(locale);
+	}
+
+	public void addInitParameter(String name, String value) {
+		Assert.notNull(name, "Parameter name must not be null");
+		this.initParameters.put(name, value);
+	}
+
+	@Override
+	public String getInitParameter(String name) {
+		Assert.notNull(name, "Parameter name must not be null");
+		return this.initParameters.get(name);
+	}
+
+	@Override
+	public Enumeration<String> getInitParameterNames() {
+		return Collections.enumeration(this.initParameters.keySet());
+	}
+
+	public void addPublicRenderParameterName(String name) {
+		this.publicRenderParameterNames.add(name);
+	}
+
+	@Override
+	public Enumeration<String> getPublicRenderParameterNames() {
+		return Collections.enumeration(this.publicRenderParameterNames);
+	}
+
+	public void setDefaultNamespace(String defaultNamespace) {
+		this.defaultNamespace = defaultNamespace;
+	}
+
+	@Override
+	public String getDefaultNamespace() {
+		return this.defaultNamespace;
+	}
+
+	public void addPublishingEventQName(QName name) {
+		this.publishingEventQNames.add(name);
+	}
+
+	@Override
+	public Enumeration<QName> getPublishingEventQNames() {
+		return Collections.enumeration(this.publishingEventQNames);
+	}
+
+	public void addProcessingEventQName(QName name) {
+		this.processingEventQNames.add(name);
+	}
+
+	@Override
+	public Enumeration<QName> getProcessingEventQNames() {
+		return Collections.enumeration(this.processingEventQNames);
+	}
+
+	public void addSupportedLocale(Locale locale) {
+		this.supportedLocales.add(locale);
+	}
+
+	@Override
+	public Enumeration<Locale> getSupportedLocales() {
+		return Collections.enumeration(this.supportedLocales);
+	}
+
+	public void addContainerRuntimeOption(String key, String value) {
+		this.containerRuntimeOptions.put(key, new String[] {value});
+	}
+
+	public void addContainerRuntimeOption(String key, String[] values) {
+		this.containerRuntimeOptions.put(key, values);
+	}
+
+	@Override
+	public Map<String, String[]> getContainerRuntimeOptions() {
+		return Collections.unmodifiableMap(this.containerRuntimeOptions);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletContext.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletContext.java
new file mode 100644
index 0000000..3cf0a5d
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletContext.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequestDispatcher;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletContext} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletContext implements PortletContext {
+
+	private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
+
+
+	private final Log logger = LogFactory.getLog(getClass());
+
+	private final String resourceBasePath;
+
+	private final ResourceLoader resourceLoader;
+
+	private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+	private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+	private String portletContextName = "MockPortletContext";
+
+	private Set<String> containerRuntimeOptions = new LinkedHashSet<String>();
+
+
+	/**
+	 * Create a new MockPortletContext with no base path and a
+	 * DefaultResourceLoader (i.e. the classpath root as WAR root).
+	 * @see org.springframework.core.io.DefaultResourceLoader
+	 */
+	public MockPortletContext() {
+		this("", null);
+	}
+
+	/**
+	 * Create a new MockPortletContext using a DefaultResourceLoader.
+	 * @param resourceBasePath the WAR root directory (should not end with a slash)
+	 * @see org.springframework.core.io.DefaultResourceLoader
+	 */
+	public MockPortletContext(String resourceBasePath) {
+		this(resourceBasePath, null);
+	}
+
+	/**
+	 * Create a new MockPortletContext, using the specified ResourceLoader
+	 * and no base path.
+	 * @param resourceLoader the ResourceLoader to use (or null for the default)
+	 */
+	public MockPortletContext(ResourceLoader resourceLoader) {
+		this("", resourceLoader);
+	}
+
+	/**
+	 * Create a new MockPortletContext.
+	 * @param resourceBasePath the WAR root directory (should not end with a slash)
+	 * @param resourceLoader the ResourceLoader to use (or null for the default)
+	 */
+	public MockPortletContext(String resourceBasePath, ResourceLoader resourceLoader) {
+		this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
+		this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
+
+		// Use JVM temp dir as PortletContext temp dir.
+		String tempDir = System.getProperty(TEMP_DIR_SYSTEM_PROPERTY);
+		if (tempDir != null) {
+			this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
+		}
+	}
+
+	/**
+	 * Build a full resource location for the given path,
+	 * prepending the resource base path of this MockPortletContext.
+	 * @param path the path as specified
+	 * @return the full resource path
+	 */
+	protected String getResourceLocation(String path) {
+		if (!path.startsWith("/")) {
+			path = "/" + path;
+		}
+		return this.resourceBasePath + path;
+	}
+
+
+	@Override
+	public String getServerInfo() {
+		return "MockPortal/1.0";
+	}
+
+	@Override
+	public PortletRequestDispatcher getRequestDispatcher(String path) {
+		if (!path.startsWith("/")) {
+			throw new IllegalArgumentException(
+					"PortletRequestDispatcher path at PortletContext level must start with '/'");
+		}
+		return new MockPortletRequestDispatcher(path);
+	}
+
+	@Override
+	public PortletRequestDispatcher getNamedDispatcher(String path) {
+		return null;
+	}
+
+	@Override
+	public InputStream getResourceAsStream(String path) {
+		Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+		try {
+			return resource.getInputStream();
+		}
+		catch (IOException ex) {
+			logger.info("Couldn't open InputStream for " + resource, ex);
+			return null;
+		}
+	}
+
+	@Override
+	public int getMajorVersion() {
+		return 2;
+	}
+
+	@Override
+	public int getMinorVersion() {
+		return 0;
+	}
+
+	@Override
+	public String getMimeType(String filePath) {
+		return null;
+	}
+
+	@Override
+	public String getRealPath(String path) {
+		Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+		try {
+			return resource.getFile().getAbsolutePath();
+		}
+		catch (IOException ex) {
+			logger.info("Couldn't determine real path of resource " + resource, ex);
+			return null;
+		}
+	}
+
+	@Override
+	public Set<String> getResourcePaths(String path) {
+		Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+		try {
+			File file = resource.getFile();
+			String[] fileList = file.list();
+			String prefix = (path.endsWith("/") ? path : path + "/");
+			Set<String> resourcePaths = new HashSet<String>(fileList.length);
+			for (String fileEntry : fileList) {
+				resourcePaths.add(prefix + fileEntry);
+			}
+			return resourcePaths;
+		}
+		catch (IOException ex) {
+			logger.info("Couldn't get resource paths for " + resource, ex);
+			return null;
+		}
+	}
+
+	@Override
+	public URL getResource(String path) throws MalformedURLException {
+		Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+		try {
+			return resource.getURL();
+		}
+		catch (IOException ex) {
+			logger.info("Couldn't get URL for " + resource, ex);
+			return null;
+		}
+	}
+
+	@Override
+	public Object getAttribute(String name) {
+		return this.attributes.get(name);
+	}
+
+	@Override
+	public Enumeration<String> getAttributeNames() {
+		return Collections.enumeration(this.attributes.keySet());
+	}
+
+	@Override
+	public void setAttribute(String name, Object value) {
+		if (value != null) {
+			this.attributes.put(name, value);
+		}
+		else {
+			this.attributes.remove(name);
+		}
+	}
+
+	@Override
+	public void removeAttribute(String name) {
+		this.attributes.remove(name);
+	}
+
+	public void addInitParameter(String name, String value) {
+		Assert.notNull(name, "Parameter name must not be null");
+		this.initParameters.put(name, value);
+	}
+
+	@Override
+	public String getInitParameter(String name) {
+		Assert.notNull(name, "Parameter name must not be null");
+		return this.initParameters.get(name);
+	}
+
+	@Override
+	public Enumeration<String> getInitParameterNames() {
+		return Collections.enumeration(this.initParameters.keySet());
+	}
+
+	@Override
+	public void log(String message) {
+		logger.info(message);
+	}
+
+	@Override
+	public void log(String message, Throwable t) {
+		logger.info(message, t);
+	}
+
+	public void setPortletContextName(String portletContextName) {
+		this.portletContextName = portletContextName;
+	}
+
+	@Override
+	public String getPortletContextName() {
+		return this.portletContextName;
+	}
+
+	public void addContainerRuntimeOption(String key) {
+		this.containerRuntimeOptions.add(key);
+	}
+
+	@Override
+	public Enumeration<String> getContainerRuntimeOptions() {
+		return Collections.enumeration(this.containerRuntimeOptions);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletPreferences.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletPreferences.java
new file mode 100644
index 0000000..60ceca2
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletPreferences.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PreferencesValidator;
+import javax.portlet.ReadOnlyException;
+import javax.portlet.ValidatorException;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletPreferences} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletPreferences implements PortletPreferences {
+
+	private PreferencesValidator preferencesValidator;
+
+	private final Map<String, String[]> preferences = new LinkedHashMap<String, String[]>();
+
+	private final Set<String> readOnly = new HashSet<String>();
+
+
+	public void setReadOnly(String key, boolean readOnly) {
+		Assert.notNull(key, "Key must not be null");
+		if (readOnly) {
+			this.readOnly.add(key);
+		}
+		else {
+			this.readOnly.remove(key);
+		}
+	}
+
+	@Override
+	public boolean isReadOnly(String key) {
+		Assert.notNull(key, "Key must not be null");
+		return this.readOnly.contains(key);
+	}
+
+	@Override
+	public String getValue(String key, String def) {
+		Assert.notNull(key, "Key must not be null");
+		String[] values = this.preferences.get(key);
+		return (values != null && values.length > 0 ? values[0] : def);
+	}
+
+	@Override
+	public String[] getValues(String key, String[] def) {
+		Assert.notNull(key, "Key must not be null");
+		String[] values = this.preferences.get(key);
+		return (values != null && values.length > 0 ? values : def);
+	}
+
+	@Override
+	public void setValue(String key, String value) throws ReadOnlyException {
+		setValues(key, new String[] {value});
+	}
+
+	@Override
+	public void setValues(String key, String[] values) throws ReadOnlyException {
+		Assert.notNull(key, "Key must not be null");
+		if (isReadOnly(key)) {
+			throw new ReadOnlyException("Preference '" + key + "' is read-only");
+		}
+		this.preferences.put(key, values);
+	}
+
+	@Override
+	public Enumeration<String> getNames() {
+		return Collections.enumeration(this.preferences.keySet());
+	}
+
+	@Override
+	public Map<String, String[]> getMap() {
+		return Collections.unmodifiableMap(this.preferences);
+	}
+
+	@Override
+	public void reset(String key) throws ReadOnlyException {
+		Assert.notNull(key, "Key must not be null");
+		if (isReadOnly(key)) {
+			throw new ReadOnlyException("Preference '" + key + "' is read-only");
+		}
+		this.preferences.remove(key);
+	}
+
+	public void setPreferencesValidator(PreferencesValidator preferencesValidator) {
+		this.preferencesValidator = preferencesValidator;
+	}
+
+	@Override
+	public void store() throws IOException, ValidatorException {
+		if (this.preferencesValidator != null) {
+			this.preferencesValidator.validate(this);
+		}
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequest.java
new file mode 100644
index 0000000..02ac4a3
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequest.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.WindowState;
+import javax.servlet.http.Cookie;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletRequest implements PortletRequest {
+
+	private boolean active = true;
+
+	private final PortalContext portalContext;
+
+	private final PortletContext portletContext;
+
+	private PortletSession session;
+
+	private WindowState windowState = WindowState.NORMAL;
+
+	private PortletMode portletMode = PortletMode.VIEW;
+
+	private PortletPreferences portletPreferences = new MockPortletPreferences();
+
+	private final Map<String, List<String>> properties = new LinkedHashMap<String, List<String>>();
+
+	private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+	private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
+
+	private String authType = null;
+
+	private String contextPath = "";
+
+	private String remoteUser = null;
+
+	private Principal userPrincipal = null;
+
+	private final Set<String> userRoles = new HashSet<String>();
+
+	private boolean secure = false;
+
+	private boolean requestedSessionIdValid = true;
+
+	private final List<String> responseContentTypes = new LinkedList<String>();
+
+	private final List<Locale> locales = new LinkedList<Locale>();
+
+	private String scheme = "http";
+
+	private String serverName = "localhost";
+
+	private int serverPort = 80;
+
+	private String windowID;
+
+	private Cookie[] cookies;
+
+	private final Set<String> publicParameterNames = new HashSet<String>();
+
+
+	/**
+	 * Create a new MockPortletRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @see MockPortalContext
+	 * @see MockPortletContext
+	 */
+	public MockPortletRequest() {
+		this(null, null);
+	}
+
+	/**
+	 * Create a new MockPortletRequest with a default {@link MockPortalContext}.
+	 * @param portletContext the PortletContext that the request runs in
+	 * @see MockPortalContext
+	 */
+	public MockPortletRequest(PortletContext portletContext) {
+		this(null, portletContext);
+	}
+
+	/**
+	 * Create a new MockPortletRequest.
+	 * @param portalContext the PortalContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockPortletRequest(PortalContext portalContext, PortletContext portletContext) {
+		this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
+		this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+		this.responseContentTypes.add("text/html");
+		this.locales.add(Locale.ENGLISH);
+		this.attributes.put(LIFECYCLE_PHASE, getLifecyclePhase());
+	}
+
+
+	//---------------------------------------------------------------------
+	// Lifecycle methods
+	//---------------------------------------------------------------------
+
+	/**
+	 * Return the Portlet 2.0 lifecycle id for the current phase.
+	 */
+	protected String getLifecyclePhase() {
+		return null;
+	}
+
+	/**
+	 * Return whether this request is still active (that is, not completed yet).
+	 */
+	public boolean isActive() {
+		return this.active;
+	}
+
+	/**
+	 * Mark this request as completed.
+	 */
+	public void close() {
+		this.active = false;
+	}
+
+	/**
+	 * Check whether this request is still active (that is, not completed yet),
+	 * throwing an IllegalStateException if not active anymore.
+	 */
+	protected void checkActive() throws IllegalStateException {
+		if (!this.active) {
+			throw new IllegalStateException("Request is not active anymore");
+		}
+	}
+
+
+	//---------------------------------------------------------------------
+	// PortletRequest methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public boolean isWindowStateAllowed(WindowState windowState) {
+		return CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState);
+	}
+
+	@Override
+	public boolean isPortletModeAllowed(PortletMode portletMode) {
+		return CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode);
+	}
+
+	public void setPortletMode(PortletMode portletMode) {
+		Assert.notNull(portletMode, "PortletMode must not be null");
+		this.portletMode = portletMode;
+	}
+
+	@Override
+	public PortletMode getPortletMode() {
+		return this.portletMode;
+	}
+
+	public void setWindowState(WindowState windowState) {
+		Assert.notNull(windowState, "WindowState must not be null");
+		this.windowState = windowState;
+	}
+
+	@Override
+	public WindowState getWindowState() {
+		return this.windowState;
+	}
+
+	public void setPreferences(PortletPreferences preferences) {
+		Assert.notNull(preferences, "PortletPreferences must not be null");
+		this.portletPreferences = preferences;
+	}
+
+	@Override
+	public PortletPreferences getPreferences() {
+		return this.portletPreferences;
+	}
+
+	public void setSession(PortletSession session) {
+		this.session = session;
+		if (session instanceof MockPortletSession) {
+			MockPortletSession mockSession = ((MockPortletSession) session);
+			mockSession.access();
+		}
+	}
+
+	@Override
+	public PortletSession getPortletSession() {
+		return getPortletSession(true);
+	}
+
+	@Override
+	public PortletSession getPortletSession(boolean create) {
+		checkActive();
+		// Reset session if invalidated.
+		if (this.session instanceof MockPortletSession && ((MockPortletSession) this.session).isInvalid()) {
+			this.session = null;
+		}
+		// Create new session if necessary.
+		if (this.session == null && create) {
+			this.session = new MockPortletSession(this.portletContext);
+		}
+		return this.session;
+	}
+
+	/**
+	 * Set a single value for the specified property.
+	 * <p>If there are already one or more values registered for the given
+	 * property key, they will be replaced.
+	 */
+	public void setProperty(String key, String value) {
+		Assert.notNull(key, "Property key must not be null");
+		List<String> list = new LinkedList<String>();
+		list.add(value);
+		this.properties.put(key, list);
+	}
+
+	/**
+	 * Add a single value for the specified property.
+	 * <p>If there are already one or more values registered for the given
+	 * property key, the given value will be added to the end of the list.
+	 */
+	public void addProperty(String key, String value) {
+		Assert.notNull(key, "Property key must not be null");
+		List<String> oldList = this.properties.get(key);
+		if (oldList != null) {
+			oldList.add(value);
+		}
+		else {
+			List<String> list = new LinkedList<String>();
+			list.add(value);
+			this.properties.put(key, list);
+		}
+	}
+
+	@Override
+	public String getProperty(String key) {
+		Assert.notNull(key, "Property key must not be null");
+		List<String> list = this.properties.get(key);
+		return (list != null && list.size() > 0 ? list.get(0) : null);
+	}
+
+	@Override
+	public Enumeration<String> getProperties(String key) {
+		Assert.notNull(key, "property key must not be null");
+		return Collections.enumeration(this.properties.get(key));
+	}
+
+	@Override
+	public Enumeration<String> getPropertyNames() {
+		return Collections.enumeration(this.properties.keySet());
+	}
+
+	@Override
+	public PortalContext getPortalContext() {
+		return this.portalContext;
+	}
+
+	public void setAuthType(String authType) {
+		this.authType = authType;
+	}
+
+	@Override
+	public String getAuthType() {
+		return this.authType;
+	}
+
+	public void setContextPath(String contextPath) {
+		this.contextPath = contextPath;
+	}
+
+	@Override
+	public String getContextPath() {
+		return this.contextPath;
+	}
+
+	public void setRemoteUser(String remoteUser) {
+		this.remoteUser = remoteUser;
+	}
+
+	@Override
+	public String getRemoteUser() {
+		return this.remoteUser;
+	}
+
+	public void setUserPrincipal(Principal userPrincipal) {
+		this.userPrincipal = userPrincipal;
+	}
+
+	@Override
+	public Principal getUserPrincipal() {
+		return this.userPrincipal;
+	}
+
+	public void addUserRole(String role) {
+		this.userRoles.add(role);
+	}
+
+	@Override
+	public boolean isUserInRole(String role) {
+		return this.userRoles.contains(role);
+	}
+
+	@Override
+	public Object getAttribute(String name) {
+		checkActive();
+		return this.attributes.get(name);
+	}
+
+	@Override
+	public Enumeration<String> getAttributeNames() {
+		checkActive();
+		return Collections.enumeration(this.attributes.keySet());
+	}
+
+	public void setParameters(Map<String, String[]> parameters) {
+		Assert.notNull(parameters, "Parameters Map must not be null");
+		this.parameters.clear();
+		this.parameters.putAll(parameters);
+	}
+
+	public void setParameter(String key, String value) {
+		Assert.notNull(key, "Parameter key must be null");
+		Assert.notNull(value, "Parameter value must not be null");
+		this.parameters.put(key, new String[] {value});
+	}
+
+	public void setParameter(String key, String[] values) {
+		Assert.notNull(key, "Parameter key must be null");
+		Assert.notNull(values, "Parameter values must not be null");
+		this.parameters.put(key, values);
+	}
+
+	public void addParameter(String name, String value) {
+		addParameter(name, new String[] {value});
+	}
+
+	public void addParameter(String name, String[] values) {
+		String[] oldArr = this.parameters.get(name);
+		if (oldArr != null) {
+			String[] newArr = new String[oldArr.length + values.length];
+			System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+			System.arraycopy(values, 0, newArr, oldArr.length, values.length);
+			this.parameters.put(name, newArr);
+		}
+		else {
+			this.parameters.put(name, values);
+		}
+	}
+
+	@Override
+	public String getParameter(String name) {
+		String[] arr = this.parameters.get(name);
+		return (arr != null && arr.length > 0 ? arr[0] : null);
+	}
+
+	@Override
+	public Enumeration<String> getParameterNames() {
+		return Collections.enumeration(this.parameters.keySet());
+	}
+
+	@Override
+	public String[] getParameterValues(String name) {
+		return this.parameters.get(name);
+	}
+
+	@Override
+	public Map<String, String[]> getParameterMap() {
+		return Collections.unmodifiableMap(this.parameters);
+	}
+
+	public void setSecure(boolean secure) {
+		this.secure = secure;
+	}
+
+	@Override
+	public boolean isSecure() {
+		return this.secure;
+	}
+
+	@Override
+	public void setAttribute(String name, Object value) {
+		checkActive();
+		if (value != null) {
+			this.attributes.put(name, value);
+		}
+		else {
+			this.attributes.remove(name);
+		}
+	}
+
+	@Override
+	public void removeAttribute(String name) {
+		checkActive();
+		this.attributes.remove(name);
+	}
+
+	@Override
+	public String getRequestedSessionId() {
+		PortletSession session = this.getPortletSession();
+		return (session != null ? session.getId() : null);
+	}
+
+	public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
+		this.requestedSessionIdValid = requestedSessionIdValid;
+	}
+
+	@Override
+	public boolean isRequestedSessionIdValid() {
+		return this.requestedSessionIdValid;
+	}
+
+	public void addResponseContentType(String responseContentType) {
+		this.responseContentTypes.add(responseContentType);
+	}
+
+	public void addPreferredResponseContentType(String responseContentType) {
+		this.responseContentTypes.add(0, responseContentType);
+	}
+
+	@Override
+	public String getResponseContentType() {
+		return this.responseContentTypes.get(0);
+	}
+
+	@Override
+	public Enumeration<String> getResponseContentTypes() {
+		return Collections.enumeration(this.responseContentTypes);
+	}
+
+	public void addLocale(Locale locale) {
+		this.locales.add(locale);
+	}
+
+	public void addPreferredLocale(Locale locale) {
+		this.locales.add(0, locale);
+	}
+
+	@Override
+	public Locale getLocale() {
+		return this.locales.get(0);
+	}
+
+	@Override
+	public Enumeration<Locale> getLocales() {
+		return Collections.enumeration(this.locales);
+	}
+
+	public void setScheme(String scheme) {
+		this.scheme = scheme;
+	}
+
+	@Override
+	public String getScheme() {
+		return this.scheme;
+	}
+
+	public void setServerName(String serverName) {
+		this.serverName = serverName;
+	}
+
+	@Override
+	public String getServerName() {
+		return this.serverName;
+	}
+
+	public void setServerPort(int serverPort) {
+		this.serverPort = serverPort;
+	}
+
+	@Override
+	public int getServerPort() {
+		return this.serverPort;
+	}
+
+	public void setWindowID(String windowID) {
+		this.windowID = windowID;
+	}
+
+	@Override
+	public String getWindowID() {
+		return this.windowID;
+	}
+
+	public void setCookies(Cookie... cookies) {
+		this.cookies = cookies;
+	}
+
+	@Override
+	public Cookie[] getCookies() {
+		return this.cookies;
+	}
+
+	@Override
+	public Map<String, String[]> getPrivateParameterMap() {
+		if (!this.publicParameterNames.isEmpty()) {
+			Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
+			for (String key : this.parameters.keySet()) {
+				if (!this.publicParameterNames.contains(key)) {
+					filtered.put(key, this.parameters.get(key));
+				}
+			}
+			return filtered;
+		}
+		else {
+			return Collections.unmodifiableMap(this.parameters);
+		}
+	}
+
+	@Override
+	public Map<String, String[]> getPublicParameterMap() {
+		if (!this.publicParameterNames.isEmpty()) {
+			Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
+			for (String key : this.parameters.keySet()) {
+				if (this.publicParameterNames.contains(key)) {
+					filtered.put(key, this.parameters.get(key));
+				}
+			}
+			return filtered;
+		}
+		else {
+			return Collections.emptyMap();
+		}
+	}
+
+	public void registerPublicParameter(String name) {
+		this.publicParameterNames.add(name);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequestDispatcher.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequestDispatcher.java
new file mode 100644
index 0000000..e80f774
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletRequestDispatcher.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.IOException;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletRequestDispatcher} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletRequestDispatcher implements PortletRequestDispatcher {
+
+	private final Log logger = LogFactory.getLog(getClass());
+
+	private final String url;
+
+
+	/**
+	 * Create a new MockPortletRequestDispatcher for the given URL.
+	 * @param url the URL to dispatch to.
+	 */
+	public MockPortletRequestDispatcher(String url) {
+		Assert.notNull(url, "URL must not be null");
+		this.url = url;
+	}
+
+
+	@Override
+	public void include(RenderRequest request, RenderResponse response) throws PortletException, IOException {
+		include((PortletRequest) request, (PortletResponse) response);
+	}
+
+	@Override
+	public void include(PortletRequest request, PortletResponse response) throws PortletException, IOException {
+		Assert.notNull(request, "Request must not be null");
+		Assert.notNull(response, "Response must not be null");
+		if (!(response instanceof MockMimeResponse)) {
+			throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
+		}
+		((MockMimeResponse) response).setIncludedUrl(this.url);
+		if (logger.isDebugEnabled()) {
+			logger.debug("MockPortletRequestDispatcher: including URL [" + this.url + "]");
+		}
+	}
+
+	@Override
+	public void forward(PortletRequest request, PortletResponse response) throws PortletException, IOException {
+		Assert.notNull(request, "Request must not be null");
+		Assert.notNull(response, "Response must not be null");
+		if (!(response instanceof MockMimeResponse)) {
+			throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
+		}
+		((MockMimeResponse) response).setForwardedUrl(this.url);
+		if (logger.isDebugEnabled()) {
+			logger.debug("MockPortletRequestDispatcher: forwarding to URL [" + this.url + "]");
+		}
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletResponse.java
new file mode 100644
index 0000000..1e69a53
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletResponse.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletResponse;
+import javax.servlet.http.Cookie;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletResponse implements PortletResponse {
+
+	private final PortalContext portalContext;
+
+	private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
+
+	private String namespace = "";
+
+	private final Set<Cookie> cookies = new LinkedHashSet<Cookie>();
+
+	private final Map<String, Element[]> xmlProperties = new LinkedHashMap<String, Element[]>();
+
+	private Document xmlDocument;
+
+
+	/**
+	 * Create a new MockPortletResponse with a default {@link MockPortalContext}.
+	 * @see MockPortalContext
+	 */
+	public MockPortletResponse() {
+		this(null);
+	}
+
+	/**
+	 * Create a new MockPortletResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 */
+	public MockPortletResponse(PortalContext portalContext) {
+		this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
+	}
+
+	/**
+	 * Return the PortalContext that this MockPortletResponse runs in,
+	 * defining the supported PortletModes and WindowStates.
+	 */
+	public PortalContext getPortalContext() {
+		return this.portalContext;
+	}
+
+
+	//---------------------------------------------------------------------
+	// PortletResponse methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void addProperty(String key, String value) {
+		Assert.notNull(key, "Property key must not be null");
+		String[] oldArr = this.properties.get(key);
+		if (oldArr != null) {
+			String[] newArr = new String[oldArr.length + 1];
+			System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+			newArr[oldArr.length] = value;
+			this.properties.put(key, newArr);
+		}
+		else {
+			this.properties.put(key, new String[] {value});
+		}
+	}
+
+	@Override
+	public void setProperty(String key, String value) {
+		Assert.notNull(key, "Property key must not be null");
+		this.properties.put(key, new String[] {value});
+	}
+
+	public Set<String> getPropertyNames() {
+		return Collections.unmodifiableSet(this.properties.keySet());
+	}
+
+	public String getProperty(String key) {
+		Assert.notNull(key, "Property key must not be null");
+		String[] arr = this.properties.get(key);
+		return (arr != null && arr.length > 0 ? arr[0] : null);
+	}
+
+	public String[] getProperties(String key) {
+		Assert.notNull(key, "Property key must not be null");
+		return this.properties.get(key);
+	}
+
+	@Override
+	public String encodeURL(String path) {
+		return path;
+	}
+
+	public void setNamespace(String namespace) {
+		this.namespace = namespace;
+	}
+
+	@Override
+	public String getNamespace() {
+		return this.namespace;
+	}
+
+	@Override
+	public void addProperty(Cookie cookie) {
+		Assert.notNull(cookie, "Cookie must not be null");
+		this.cookies.add(cookie);
+	}
+
+	public Cookie[] getCookies() {
+		return this.cookies.toArray(new Cookie[this.cookies.size()]);
+	}
+
+	public Cookie getCookie(String name) {
+		Assert.notNull(name, "Cookie name must not be null");
+		for (Cookie cookie : this.cookies) {
+			if (name.equals(cookie.getName())) {
+				return cookie;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public void addProperty(String key, Element value) {
+		Assert.notNull(key, "Property key must not be null");
+		Element[] oldArr = this.xmlProperties.get(key);
+		if (oldArr != null) {
+			Element[] newArr = new Element[oldArr.length + 1];
+			System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+			newArr[oldArr.length] = value;
+			this.xmlProperties.put(key, newArr);
+		}
+		else {
+			this.xmlProperties.put(key, new Element[] {value});
+		}
+	}
+
+
+	public Set<String> getXmlPropertyNames() {
+		return Collections.unmodifiableSet(this.xmlProperties.keySet());
+	}
+
+	public Element getXmlProperty(String key) {
+		Assert.notNull(key, "Property key must not be null");
+		Element[] arr = this.xmlProperties.get(key);
+		return (arr != null && arr.length > 0 ? arr[0] : null);
+	}
+
+	public Element[] getXmlProperties(String key) {
+		Assert.notNull(key, "Property key must not be null");
+		return this.xmlProperties.get(key);
+	}
+
+	@Override
+	public Element createElement(String tagName) throws DOMException {
+		if (this.xmlDocument == null) {
+			try {
+				this.xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+			}
+			catch (ParserConfigurationException ex) {
+				throw new DOMException(DOMException.INVALID_STATE_ERR, ex.toString());
+			}
+		}
+		return this.xmlDocument.createElement(tagName);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletSession.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletSession.java
new file mode 100644
index 0000000..ca1a44a
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletSession.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.springframework.mock.web.MockHttpSession;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletSession} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletSession implements PortletSession {
+
+	private static int nextId = 1;
+
+
+	private final String id = Integer.toString(nextId++);
+
+	private final long creationTime = System.currentTimeMillis();
+
+	private int maxInactiveInterval;
+
+	private long lastAccessedTime = System.currentTimeMillis();
+
+	private final PortletContext portletContext;
+
+	private final Map<String, Object> portletAttributes = new HashMap<String, Object>();
+
+	private final Map<String, Object> applicationAttributes = new HashMap<String, Object>();
+
+	private boolean invalid = false;
+
+	private boolean isNew = true;
+
+
+	/**
+	 * Create a new MockPortletSession with a default {@link MockPortletContext}.
+	 * @see MockPortletContext
+	 */
+	public MockPortletSession() {
+		this(null);
+	}
+
+	/**
+	 * Create a new MockPortletSession.
+	 * @param portletContext the PortletContext that the session runs in
+	 */
+	public MockPortletSession(PortletContext portletContext) {
+		this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+	}
+
+
+	@Override
+	public Object getAttribute(String name) {
+		return this.portletAttributes.get(name);
+	}
+
+	@Override
+	public Object getAttribute(String name, int scope) {
+		if (scope == PortletSession.PORTLET_SCOPE) {
+			return this.portletAttributes.get(name);
+		}
+		else if (scope == PortletSession.APPLICATION_SCOPE) {
+			return this.applicationAttributes.get(name);
+		}
+		return null;
+	}
+
+	@Override
+	public Enumeration<String> getAttributeNames() {
+		return Collections.enumeration(this.portletAttributes.keySet());
+	}
+
+	@Override
+	public Enumeration<String> getAttributeNames(int scope) {
+		if (scope == PortletSession.PORTLET_SCOPE) {
+			return Collections.enumeration(this.portletAttributes.keySet());
+		}
+		else if (scope == PortletSession.APPLICATION_SCOPE) {
+			return Collections.enumeration(this.applicationAttributes.keySet());
+		}
+		return null;
+	}
+
+	@Override
+	public long getCreationTime() {
+		return this.creationTime;
+	}
+
+	@Override
+	public String getId() {
+		return this.id;
+	}
+
+	public void access() {
+		this.lastAccessedTime = System.currentTimeMillis();
+		setNew(false);
+	}
+
+	@Override
+	public long getLastAccessedTime() {
+		return this.lastAccessedTime;
+	}
+
+	@Override
+	public int getMaxInactiveInterval() {
+		return this.maxInactiveInterval;
+	}
+
+	/**
+	 * Clear all of this session's attributes.
+	 */
+	public void clearAttributes() {
+		doClearAttributes(this.portletAttributes);
+		doClearAttributes(this.applicationAttributes);
+	}
+
+	protected void doClearAttributes(Map<String, Object> attributes) {
+		for (Iterator<Map.Entry<String, Object>> it = attributes.entrySet().iterator(); it.hasNext();) {
+			Map.Entry<String, Object> entry = it.next();
+			String name = entry.getKey();
+			Object value = entry.getValue();
+			it.remove();
+			if (value instanceof HttpSessionBindingListener) {
+				((HttpSessionBindingListener) value).valueUnbound(
+						new HttpSessionBindingEvent(new MockHttpSession(), name, value));
+			}
+		}
+	}
+
+	@Override
+	public void invalidate() {
+		this.invalid = true;
+		clearAttributes();
+	}
+
+	public boolean isInvalid() {
+		return this.invalid;
+	}
+
+	public void setNew(boolean value) {
+		this.isNew = value;
+	}
+
+	@Override
+	public boolean isNew() {
+		return this.isNew;
+	}
+
+	@Override
+	public void removeAttribute(String name) {
+		this.portletAttributes.remove(name);
+	}
+
+	@Override
+	public void removeAttribute(String name, int scope) {
+		if (scope == PortletSession.PORTLET_SCOPE) {
+			this.portletAttributes.remove(name);
+		}
+		else if (scope == PortletSession.APPLICATION_SCOPE) {
+			this.applicationAttributes.remove(name);
+		}
+	}
+
+	@Override
+	public void setAttribute(String name, Object value) {
+		if (value != null) {
+			this.portletAttributes.put(name, value);
+		}
+		else {
+			this.portletAttributes.remove(name);
+		}
+	}
+
+	@Override
+	public void setAttribute(String name, Object value, int scope) {
+		if (scope == PortletSession.PORTLET_SCOPE) {
+			if (value != null) {
+				this.portletAttributes.put(name, value);
+			}
+			else {
+				this.portletAttributes.remove(name);
+			}
+		}
+		else if (scope == PortletSession.APPLICATION_SCOPE) {
+			if (value != null) {
+				this.applicationAttributes.put(name, value);
+			}
+			else {
+				this.applicationAttributes.remove(name);
+			}
+		}
+	}
+
+	@Override
+	public void setMaxInactiveInterval(int interval) {
+		this.maxInactiveInterval = interval;
+	}
+
+	@Override
+	public PortletContext getPortletContext() {
+		return this.portletContext;
+	}
+
+	@Override
+	public Map<String, Object> getAttributeMap() {
+		return Collections.unmodifiableMap(this.portletAttributes);
+	}
+
+	@Override
+	public Map<String, Object> getAttributeMap(int scope) {
+		if (scope == PortletSession.PORTLET_SCOPE) {
+			return Collections.unmodifiableMap(this.portletAttributes);
+		}
+		else if (scope == PortletSession.APPLICATION_SCOPE) {
+			return Collections.unmodifiableMap(this.applicationAttributes);
+		}
+		else {
+			return Collections.emptyMap();
+		}
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletURL.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletURL.java
new file mode 100644
index 0000000..503807e
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockPortletURL.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.PortletURL;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletURL} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletURL extends MockBaseURL implements PortletURL {
+
+	public static final String URL_TYPE_RENDER = "render";
+
+	public static final String URL_TYPE_ACTION = "action";
+
+
+	private final PortalContext portalContext;
+
+	private final String urlType;
+
+	private WindowState windowState;
+
+	private PortletMode portletMode;
+
+
+	/**
+	 * Create a new MockPortletURL for the given URL type.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 * @param urlType the URL type, for example "render" or "action"
+	 * @see #URL_TYPE_RENDER
+	 * @see #URL_TYPE_ACTION
+	 */
+	public MockPortletURL(PortalContext portalContext, String urlType) {
+		Assert.notNull(portalContext, "PortalContext is required");
+		this.portalContext = portalContext;
+		this.urlType = urlType;
+	}
+
+
+	//---------------------------------------------------------------------
+	// PortletURL methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void setWindowState(WindowState windowState) throws WindowStateException {
+		if (!CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState)) {
+			throw new WindowStateException("WindowState not supported", windowState);
+		}
+		this.windowState = windowState;
+	}
+
+	@Override
+	public WindowState getWindowState() {
+		return this.windowState;
+	}
+
+	@Override
+	public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+		if (!CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode)) {
+			throw new PortletModeException("PortletMode not supported", portletMode);
+		}
+		this.portletMode = portletMode;
+	}
+
+	@Override
+	public PortletMode getPortletMode() {
+		return this.portletMode;
+	}
+
+	@Override
+	public void removePublicRenderParameter(String name) {
+		this.parameters.remove(name);
+	}
+
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(encodeParameter("urlType", this.urlType));
+		if (this.windowState != null) {
+			sb.append(";").append(encodeParameter("windowState", this.windowState.toString()));
+		}
+		if (this.portletMode != null) {
+			sb.append(";").append(encodeParameter("portletMode", this.portletMode.toString()));
+		}
+		for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
+			sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
+		}
+		return (isSecure() ? "https:" : "http:") +
+				"//localhost/mockportlet?" + sb.toString();
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderRequest.java
new file mode 100644
index 0000000..fe5c11a
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderRequest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.RenderRequest;
+import javax.portlet.WindowState;
+
+/**
+ * Mock implementation of the {@link javax.portlet.RenderRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockRenderRequest extends MockPortletRequest implements RenderRequest {
+
+	/**
+	 * Create a new MockRenderRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @see MockPortalContext
+	 * @see MockPortletContext
+	 */
+	public MockRenderRequest() {
+		super();
+	}
+
+	/**
+	 * Create a new MockRenderRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param portletMode the mode that the portlet runs in
+	 */
+	public MockRenderRequest(PortletMode portletMode) {
+		super();
+		setPortletMode(portletMode);
+	}
+
+	/**
+	 * Create a new MockRenderRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param portletMode the mode that the portlet runs in
+	 * @param windowState the window state to run the portlet in
+	 */
+	public MockRenderRequest(PortletMode portletMode, WindowState windowState) {
+		super();
+		setPortletMode(portletMode);
+		setWindowState(windowState);
+	}
+
+	/**
+	 * Create a new MockRenderRequest with a default {@link MockPortalContext}.
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockRenderRequest(PortletContext portletContext) {
+		super(portletContext);
+	}
+
+	/**
+	 * Create a new MockRenderRequest.
+	 * @param portalContext the PortletContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockRenderRequest(PortalContext portalContext, PortletContext portletContext) {
+		super(portalContext, portletContext);
+	}
+
+
+	@Override
+	protected String getLifecyclePhase() {
+		return RENDER_PHASE;
+	}
+
+	@Override
+	public String getETag() {
+		return getProperty(RenderRequest.ETAG);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderResponse.java
new file mode 100644
index 0000000..defa5e9
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockRenderResponse.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collection;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.RenderResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockRenderResponse extends MockMimeResponse implements RenderResponse {
+
+	private String title;
+
+	private Collection<PortletMode> nextPossiblePortletModes;
+
+
+	/**
+	 * Create a new MockRenderResponse with a default {@link MockPortalContext}.
+	 * @see MockPortalContext
+	 */
+	public MockRenderResponse() {
+		super();
+	}
+
+	/**
+	 * Create a new MockRenderResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 */
+	public MockRenderResponse(PortalContext portalContext) {
+		super(portalContext);
+	}
+
+	/**
+	 * Create a new MockRenderResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 * @param request the corresponding render request that this response
+	 * is generated for
+	 */
+	public MockRenderResponse(PortalContext portalContext, RenderRequest request) {
+		super(portalContext, request);
+	}
+
+
+	//---------------------------------------------------------------------
+	// RenderResponse methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getTitle() {
+		return this.title;
+	}
+
+	@Override
+	public void setNextPossiblePortletModes(Collection<PortletMode> portletModes) {
+		this.nextPossiblePortletModes = portletModes;
+	}
+
+	public Collection<PortletMode> getNextPossiblePortletModes() {
+		return this.nextPossiblePortletModes;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceRequest.java
new file mode 100644
index 0000000..4212170
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceRequest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.RenderRequest;
+import javax.portlet.ResourceRequest;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceRequest extends MockClientDataRequest implements ResourceRequest {
+
+	private String resourceID;
+
+	private String cacheability;
+
+	private final Map<String, String[]> privateRenderParameterMap = new LinkedHashMap<String, String[]>();
+
+
+	/**
+	 * Create a new MockResourceRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @see org.springframework.mock.web.portlet.MockPortalContext
+	 * @see org.springframework.mock.web.portlet.MockPortletContext
+	 */
+	public MockResourceRequest() {
+		super();
+	}
+
+	/**
+	 * Create a new MockResourceRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param resourceID the resource id for this request
+	 */
+	public MockResourceRequest(String resourceID) {
+		super();
+		this.resourceID = resourceID;
+	}
+
+	/**
+	 * Create a new MockResourceRequest with a default {@link MockPortalContext}
+	 * and a default {@link MockPortletContext}.
+	 * @param url the resource URL for this request
+	 */
+	public MockResourceRequest(MockResourceURL url) {
+		super();
+		this.resourceID = url.getResourceID();
+		this.cacheability = url.getCacheability();
+	}
+
+	/**
+	 * Create a new MockResourceRequest with a default {@link MockPortalContext}.
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockResourceRequest(PortletContext portletContext) {
+		super(portletContext);
+	}
+
+	/**
+	 * Create a new MockResourceRequest.
+	 * @param portalContext the PortalContext that the request runs in
+	 * @param portletContext the PortletContext that the request runs in
+	 */
+	public MockResourceRequest(PortalContext portalContext, PortletContext portletContext) {
+		super(portalContext, portletContext);
+	}
+
+
+	@Override
+	protected String getLifecyclePhase() {
+		return RESOURCE_PHASE;
+	}
+
+	public void setResourceID(String resourceID) {
+		this.resourceID = resourceID;
+	}
+
+	@Override
+	public String getResourceID() {
+		return this.resourceID;
+	}
+
+	public void setCacheability(String cacheLevel) {
+		this.cacheability = cacheLevel;
+	}
+
+	@Override
+	public String getCacheability() {
+		return this.cacheability;
+	}
+
+	@Override
+	public String getETag() {
+		return getProperty(RenderRequest.ETAG);
+	}
+
+	public void addPrivateRenderParameter(String key, String value) {
+		this.privateRenderParameterMap.put(key, new String[] {value});
+	}
+
+	public void addPrivateRenderParameter(String key, String[] values) {
+		this.privateRenderParameterMap.put(key, values);
+	}
+
+	@Override
+	public Map<String, String[]> getPrivateRenderParameterMap() {
+		return Collections.unmodifiableMap(this.privateRenderParameterMap);
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceResponse.java
new file mode 100644
index 0000000..06bba7a
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceResponse.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import javax.portlet.ResourceResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceResponse extends MockMimeResponse implements ResourceResponse {
+
+	private int contentLength = 0;
+
+
+	@Override
+	public void setContentLength(int len) {
+		this.contentLength = len;
+	}
+
+	public int getContentLength() {
+		return this.contentLength;
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceURL.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceURL.java
new file mode 100644
index 0000000..e38e089
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockResourceURL.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.util.Map;
+import javax.portlet.ResourceURL;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceURL} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceURL extends MockBaseURL implements ResourceURL {
+
+	private String resourceID;
+
+	private String cacheability;
+
+
+	//---------------------------------------------------------------------
+	// ResourceURL methods
+	//---------------------------------------------------------------------
+
+	@Override
+	public void setResourceID(String resourceID) {
+		this.resourceID = resourceID;
+	}
+
+	public String getResourceID() {
+		return this.resourceID;
+	}
+
+	@Override
+	public void setCacheability(String cacheLevel) {
+		this.cacheability = cacheLevel;
+	}
+
+	@Override
+	public String getCacheability() {
+		return this.cacheability;
+	}
+
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(encodeParameter("resourceID", this.resourceID));
+		if (this.cacheability != null) {
+			sb.append(";").append(encodeParameter("cacheability", this.cacheability));
+		}
+		for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
+			sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
+		}
+		return (isSecure() ? "https:" : "http:") +
+				"//localhost/mockportlet?" + sb.toString();
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockStateAwareResponse.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockStateAwareResponse.java
new file mode 100644
index 0000000..bb085ba
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/MockStateAwareResponse.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.StateAwareResponse;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+import javax.xml.namespace.QName;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.StateAwareResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockStateAwareResponse extends MockPortletResponse implements StateAwareResponse {
+
+	private WindowState windowState;
+
+	private PortletMode portletMode;
+
+	private final Map<String, String[]> renderParameters = new LinkedHashMap<String, String[]>();
+
+	private final Map<QName, Serializable> events = new HashMap<QName, Serializable>();
+
+
+	/**
+	 * Create a new MockActionResponse with a default {@link MockPortalContext}.
+	 * @see org.springframework.mock.web.portlet.MockPortalContext
+	 */
+	public MockStateAwareResponse() {
+		super();
+	}
+
+	/**
+	 * Create a new MockActionResponse.
+	 * @param portalContext the PortalContext defining the supported
+	 * PortletModes and WindowStates
+	 */
+	public MockStateAwareResponse(PortalContext portalContext) {
+		super(portalContext);
+	}
+
+
+	@Override
+	public void setWindowState(WindowState windowState) throws WindowStateException {
+		if (!CollectionUtils.contains(getPortalContext().getSupportedWindowStates(), windowState)) {
+			throw new WindowStateException("WindowState not supported", windowState);
+		}
+		this.windowState = windowState;
+	}
+
+	@Override
+	public WindowState getWindowState() {
+		return this.windowState;
+	}
+
+	@Override
+	public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+		if (!CollectionUtils.contains(getPortalContext().getSupportedPortletModes(), portletMode)) {
+			throw new PortletModeException("PortletMode not supported", portletMode);
+		}
+		this.portletMode = portletMode;
+	}
+
+	@Override
+	public PortletMode getPortletMode() {
+		return this.portletMode;
+	}
+
+	@Override
+	public void setRenderParameters(Map<String, String[]> parameters) {
+		Assert.notNull(parameters, "Parameters Map must not be null");
+		this.renderParameters.clear();
+		this.renderParameters.putAll(parameters);
+	}
+
+	@Override
+	public void setRenderParameter(String key, String value) {
+		Assert.notNull(key, "Parameter key must not be null");
+		Assert.notNull(value, "Parameter value must not be null");
+		this.renderParameters.put(key, new String[] {value});
+	}
+
+	@Override
+	public void setRenderParameter(String key, String[] values) {
+		Assert.notNull(key, "Parameter key must not be null");
+		Assert.notNull(values, "Parameter values must not be null");
+		this.renderParameters.put(key, values);
+	}
+
+	public String getRenderParameter(String key) {
+		Assert.notNull(key, "Parameter key must not be null");
+		String[] arr = this.renderParameters.get(key);
+		return (arr != null && arr.length > 0 ? arr[0] : null);
+	}
+
+	public String[] getRenderParameterValues(String key) {
+		Assert.notNull(key, "Parameter key must not be null");
+		return this.renderParameters.get(key);
+	}
+
+	public Iterator<String> getRenderParameterNames() {
+		return this.renderParameters.keySet().iterator();
+	}
+
+	@Override
+	public Map<String, String[]> getRenderParameterMap() {
+		return Collections.unmodifiableMap(this.renderParameters);
+	}
+
+	@Override
+	public void removePublicRenderParameter(String name) {
+		this.renderParameters.remove(name);
+	}
+
+	@Override
+	public void setEvent(QName name, Serializable value) {
+		this.events.put(name, value);
+	}
+
+	@Override
+	public void setEvent(String name, Serializable value) {
+		this.events.put(new QName(name), value);
+	}
+
+	public Iterator<QName> getEventNames() {
+		return this.events.keySet().iterator();
+	}
+
+	public Serializable getEvent(QName name) {
+		return this.events.get(name);
+	}
+
+	public Serializable getEvent(String name) {
+		return this.events.get(new QName(name));
+	}
+
+}
\ No newline at end of file
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/ServletWrappingPortletContext.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/ServletWrappingPortletContext.java
new file mode 100644
index 0000000..70f8b86
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/ServletWrappingPortletContext.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequestDispatcher;
+import javax.servlet.ServletContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletContext} interface,
+ * wrapping an underlying {@link javax.servlet.ServletContext}.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see MockPortletContext
+ */
+public class ServletWrappingPortletContext implements PortletContext {
+
+	private final ServletContext servletContext;
+
+
+	/**
+	 * Create a new PortletContext wrapping the given ServletContext.
+	 * @param servletContext the ServletContext to wrap
+	 */
+	public ServletWrappingPortletContext(ServletContext servletContext) {
+		Assert.notNull(servletContext, "ServletContext must not be null");
+		this.servletContext = servletContext;
+	}
+
+	/**
+	 * Return the underlying ServletContext that this PortletContext wraps.
+	 */
+	public final ServletContext getServletContext() {
+		return this.servletContext;
+	}
+
+
+	@Override
+	public String getServerInfo() {
+		return this.servletContext.getServerInfo();
+	}
+
+	@Override
+	public PortletRequestDispatcher getRequestDispatcher(String path) {
+		return null;
+	}
+
+	@Override
+	public PortletRequestDispatcher getNamedDispatcher(String name) {
+		return null;
+	}
+
+	@Override
+	public InputStream getResourceAsStream(String path) {
+		return this.servletContext.getResourceAsStream(path);
+	}
+
+	@Override
+	public int getMajorVersion() {
+		return 2;
+	}
+
+	@Override
+	public int getMinorVersion() {
+		return 0;
+	}
+
+	@Override
+	public String getMimeType(String file) {
+		return this.servletContext.getMimeType(file);
+	}
+
+	@Override
+	public String getRealPath(String path) {
+		return this.servletContext.getRealPath(path);
+	}
+
+	@Override
+	public Set<String> getResourcePaths(String path) {
+		return this.servletContext.getResourcePaths(path);
+	}
+
+	@Override
+	public URL getResource(String path) throws MalformedURLException {
+		return this.servletContext.getResource(path);
+	}
+
+	@Override
+	public Object getAttribute(String name) {
+		return this.servletContext.getAttribute(name);
+	}
+
+	@Override
+	public Enumeration<String> getAttributeNames() {
+		return this.servletContext.getAttributeNames();
+	}
+
+	@Override
+	public String getInitParameter(String name) {
+		return this.servletContext.getInitParameter(name);
+	}
+
+	@Override
+	public Enumeration<String> getInitParameterNames() {
+		return this.servletContext.getInitParameterNames();
+	}
+
+	@Override
+	public void log(String msg) {
+		this.servletContext.log(msg);
+	}
+
+	@Override
+	public void log(String message, Throwable throwable) {
+		this.servletContext.log(message, throwable);
+	}
+
+	@Override
+	public void removeAttribute(String name) {
+		this.servletContext.removeAttribute(name);
+	}
+
+	@Override
+	public void setAttribute(String name, Object object) {
+		this.servletContext.setAttribute(name, object);
+	}
+
+	@Override
+	public String getPortletContextName() {
+		return this.servletContext.getServletContextName();
+	}
+
+	@Override
+	public Enumeration<String> getContainerRuntimeOptions() {
+		return Collections.enumeration(Collections.<String>emptySet());
+	}
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/MultipartActionRequest.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/MultipartActionRequest.java
new file mode 100644
index 0000000..6df611f
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/MultipartActionRequest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.struts2.mock.web.portlet.multipart;
+
+import javax.portlet.ActionRequest;
+
+import org.springframework.web.multipart.MultipartRequest;
+
+/**
+ * Interface which provides additional methods for dealing with multipart
+ * content within a portlet request, allowing to access uploaded files.
+ * Implementations also need to override the standard ActionRequest
+ * methods for parameter access, making multipart parameters available.
+ *
+ * <p>A concrete implementation is {@link DefaultMultipartActionRequest}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see PortletMultipartResolver
+ * @see org.springframework.web.multipart.MultipartFile
+ * @see javax.portlet.ActionRequest#getParameter
+ * @see javax.portlet.ActionRequest#getParameterNames
+ * @see javax.portlet.ActionRequest#getParameterMap
+ */
+public interface MultipartActionRequest extends ActionRequest, MultipartRequest {
+
+}
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/package-info.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/package-info.java
new file mode 100644
index 0000000..0ed9b60
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/multipart/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * This package is a copy of the org.springframework.web.portlet.multipart package from Spring 4.3.x
+ * (org.springframework, spring-webmvc-portlet, <a href="https://github.com/spring-projects/spring-framework/tree/4.3.x</a>)
+ * which changes the package name to: org.apache.struts2.mock.web.portlet.multipart.
+ *
+ * The copyright and license notice above is reproduced from the original package's CommonsPortletMultipartResolver.java,
+ * since it contained the widest year-range.
+ *
+ * The mock objects only have a dependency on one file from the package, MultipartActionRequest.java, so
+ * it was required.  The other files in the package were not retained in this copy (CommonsPortletMultipartResolver.java
+ * in particular created additional dependencies that would require copying additional packages).
+ *
+ * With Spring 5 dropping Portlet MVC support completely [SPR-14129], copying this package should allow
+ * individuals to continue using JUnit unit tests for their Struts 2 Portlet Plugin applications with
+ * Spring 5.x.
+ */
+/**
+ * Multipart resolution framework for handling file uploads.
+ * Provides a PortletMultipartResolver strategy interface,
+ * and a generic extension of the ActionRequest interface
+ * for accessing multipart files in web application code.
+ */
+package org.apache.struts2.mock.web.portlet.multipart;
diff --git a/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/package-info.java b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/package-info.java
new file mode 100644
index 0000000..982821e
--- /dev/null
+++ b/plugins/junit-portlet/src/main/java/org/apache/struts2/mock/web/portlet/package-info.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * This package is a copy of the org.springframework.mock.web.portlet package from Spring 4.3.x
+ * (org.springframework, spring-test, <a href="https://github.com/spring-projects/spring-framework/tree/4.3.x</a>)
+ * which changes the package name to: org.apache.struts2.mock.web.portlet.
+ *
+ * The copyright and license notice above is reproduced from the original package's ServletWrappingPortletContext.java,
+ * since it contained the widest year-range.
+ *
+ * With Spring 5 dropping Portlet MVC support completely [SPR-14129], copying this package should allow
+ * individuals to continue using JUnit unit tests for their Struts 2 Portlet Plugin applications with
+ * Spring 5.x.
+ */
+/**
+ * A comprehensive set of Portlet API mock objects, targeted at usage with Spring's web MVC framework.
+ * Useful for testing web contexts and controllers.
+ *
+ * <p>More convenient to use than dynamic mock objects (<a href="http://easymock.org/">EasyMock</a>) or
+ * existing Portlet API mock objects.
+ */
+package org.apache.struts2.mock.web.portlet;
\ No newline at end of file
diff --git a/plugins/junit-portlet/src/site/site.xml b/plugins/junit-portlet/src/site/site.xml
new file mode 100644
index 0000000..54fdcf4
--- /dev/null
+++ b/plugins/junit-portlet/src/site/site.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<project name="Apache Struts">
+    <skin>
+        <groupId>org.apache.maven.skins</groupId>
+        <artifactId>maven-fluido-skin</artifactId>
+        <version>${fluido-skin.version}</version>
+    </skin>
+    <bannerLeft>
+        <name>Apache Software Foundation</name>
+        <src>http://www.apache.org/images/asf-logo.gif</src>
+        <href>http://www.apache.org/</href>
+    </bannerLeft>
+    <bannerRight>
+        <name>Apache Struts</name>
+        <src>http://struts.apache.org/img/struts-logo.svg</src>
+        <href>http://struts.apache.org/</href>
+    </bannerRight>
+    <publishDate position="left"/>
+    <version position="right"/>
+    <body>
+        <links>
+            <item name="Apache" href="http://www.apache.org/"/>
+            <item name="Struts" href="http://struts.apache.org/"/>
+        </links>
+
+        <menu ref="parent"/>
+        <menu ref="reports"/>
+
+        <footer>
+            <![CDATA[<div class="row span12">
+            Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts project
+            logos are trademarks of The Apache Software Foundation.
+            </div>]]>
+        </footer>
+    </body>
+</project>
diff --git a/plugins/junit-portlet/src/test/java/org/apache/struts2/StrutsSpringPortletMockObjectsTest.java b/plugins/junit-portlet/src/test/java/org/apache/struts2/StrutsSpringPortletMockObjectsTest.java
new file mode 100644
index 0000000..6ba60f3
--- /dev/null
+++ b/plugins/junit-portlet/src/test/java/org/apache/struts2/StrutsSpringPortletMockObjectsTest.java
@@ -0,0 +1,1596 @@
+/*
+ * Copyright 2020 Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import javax.portlet.ActionRequest;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.PortletURL;
+import javax.portlet.PreferencesValidator;
+import javax.portlet.ReadOnlyException;
+import javax.portlet.ResourceURL;
+import javax.portlet.ValidatorException;
+import javax.portlet.WindowState;
+import javax.security.auth.Subject;
+import javax.servlet.http.Cookie;
+import javax.xml.namespace.QName;
+import org.apache.struts2.mock.web.portlet.MockActionRequest;
+import org.apache.struts2.mock.web.portlet.MockActionResponse;
+import org.apache.struts2.mock.web.portlet.MockCacheControl;
+import org.apache.struts2.mock.web.portlet.MockClientDataRequest;
+import org.apache.struts2.mock.web.portlet.MockEvent;
+import org.apache.struts2.mock.web.portlet.MockEventRequest;
+import org.apache.struts2.mock.web.portlet.MockEventResponse;
+import org.apache.struts2.mock.web.portlet.MockMimeResponse;
+import org.apache.struts2.mock.web.portlet.MockMultipartActionRequest;
+import org.apache.struts2.mock.web.portlet.MockPortalContext;
+import org.apache.struts2.mock.web.portlet.MockPortletConfig;
+import org.apache.struts2.mock.web.portlet.MockPortletContext;
+import org.apache.struts2.mock.web.portlet.MockPortletPreferences;
+import org.apache.struts2.mock.web.portlet.MockPortletRequest;
+import org.apache.struts2.mock.web.portlet.MockPortletRequestDispatcher;
+import org.apache.struts2.mock.web.portlet.MockPortletResponse;
+import org.apache.struts2.mock.web.portlet.MockPortletSession;
+import org.apache.struts2.mock.web.portlet.MockPortletURL;
+import org.apache.struts2.mock.web.portlet.MockRenderRequest;
+import org.apache.struts2.mock.web.portlet.MockRenderResponse;
+import org.apache.struts2.mock.web.portlet.MockResourceRequest;
+import org.apache.struts2.mock.web.portlet.MockResourceResponse;
+import org.apache.struts2.mock.web.portlet.MockResourceURL;
+import org.apache.struts2.mock.web.portlet.MockStateAwareResponse;
+import org.apache.struts2.mock.web.portlet.ServletWrappingPortletContext;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.util.WebUtils;
+import org.w3c.dom.Element;
+
+/**
+ * Basic test class for Portlet Mock Object testing
+ * 
+ */
+public class StrutsSpringPortletMockObjectsTest extends StrutsSpringTestCase {
+
+    /**
+     * An empty preferences validator for code coverage only
+     */
+    private static class BasicPreferencesValidator implements PreferencesValidator {
+
+        @Override
+        public void validate(PortletPreferences preferences) throws ValidatorException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+    }
+
+    public void testApplicationContext() {
+        assertNotNull(applicationContext);
+    }
+
+    public void testContextLocations() {
+        assertNotNull(getContextLocations());
+    }
+
+    public void testMockActionRequest() {
+        final String TEST_ACTIONNAME = "TEST_ACTIONNAME";
+
+        // Call each constructor in sequence, do some basic checks
+        MockActionRequest mockActionRequest = new MockActionRequest();  // Only test is to confirm constructor completes without exception
+        mockActionRequest = new MockActionRequest(TEST_ACTIONNAME);
+        assertEquals("Action name (ActionRequest.ACTION_NAME) " + TEST_ACTIONNAME + " not set ?", TEST_ACTIONNAME, mockActionRequest.getParameter(ActionRequest.ACTION_NAME));
+        mockActionRequest = new MockActionRequest(PortletMode.VIEW);
+        assertEquals("PortletMode.VIEW not set ?", PortletMode.VIEW, mockActionRequest.getPortletMode());
+
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockActionRequest = new MockActionRequest(mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockActionRequest = new MockActionRequest(mockPortalContext, mockPortletContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockActionRequest.getPortalContext());
+    }
+
+    public void testMockActionResponse() {
+        final String TEST_PARAM = "TEST_PARAM";
+        final String TEST_PARAM2 = "TEST_PARAM2";
+        final String TEST_PARAM3 = "TEST_PARAM3";
+        final String TEST_PARAM_VALUE = "NORMALVALUE";
+        final String[] TEST_PARAM_ARRAYVALUE = { "ARRAYVALUE1", "ARRAYVALUE2" };
+        final String[] TEST_PARAM_ARRAYVALUE2 = { "ARRAYVALUE3", "ARRAYVALUE4" };
+        final String TEST_REDIRECT_URL = "localhost:8080/fakeurl";
+        final String TEST_RENDER_URLPARAM_NAME = "fake_render_url_param_name";
+
+        // Call each constructor in sequence, do some basic checks
+        MockActionResponse mockActionResponse = new MockActionResponse();
+        try {
+            mockActionResponse.setPortletMode(PortletMode.VIEW);
+        } catch (Exception ex) {
+            fail("MockActionResponse portlet mode set failed.  Exception: " + ex);
+        }
+        assertEquals("PortletMode.VIEW not set ?", PortletMode.VIEW, mockActionResponse.getPortletMode());
+
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockActionResponse = new MockActionResponse(mockPortalContext);  // Only test is to confirm constructor completes without exception
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockActionResponse.getPortalContext());
+        mockActionResponse.setRenderParameter(TEST_PARAM, TEST_PARAM_VALUE);
+        mockActionResponse.setRenderParameter(TEST_PARAM2, TEST_PARAM_ARRAYVALUE);
+        assertEquals("Portal test render parameter value not as expected ?", TEST_PARAM_VALUE, mockActionResponse.getRenderParameter(TEST_PARAM));
+        assertEquals("Portal test render parameter value not as expected ?", TEST_PARAM_ARRAYVALUE[0], mockActionResponse.getRenderParameter(TEST_PARAM2));
+        final Map<String, String[]> renderParametersMap = new LinkedHashMap<>();
+        renderParametersMap.put(TEST_PARAM3, TEST_PARAM_ARRAYVALUE2);
+        mockActionResponse.setRenderParameters(renderParametersMap);
+        assertEquals("Portal test render parameter value not cleared as expected ?", null, mockActionResponse.getRenderParameter(TEST_PARAM));
+        assertEquals("Portal test render parameter value not cleared as expected ?", null, mockActionResponse.getRenderParameter(TEST_PARAM2));
+        assertEquals("Portal test render parameter map value not as expected ?", renderParametersMap, mockActionResponse.getRenderParameterMap());
+
+        try {
+            mockActionResponse.sendRedirect(TEST_REDIRECT_URL);
+            fail("Able to redirect after windowState/portletMode/renderParameters set ?");
+        } catch (IllegalStateException ise) {
+            // Expected failure
+        } catch (Exception ex) {
+            fail("MockActionResponse sendredirect failed.  Exception: " + ex);
+        }
+        mockActionResponse = new MockActionResponse();  // Clean mock response
+        try {
+            mockActionResponse.sendRedirect(TEST_REDIRECT_URL);
+        } catch (Exception ex) {
+            fail("MockActionResponse sendredirect failed.  Exception: " + ex);
+        }
+        assertEquals("Redirect url not as expected ?", TEST_REDIRECT_URL, mockActionResponse.getRedirectedUrl());
+        mockActionResponse = new MockActionResponse();  // Clean mock response
+        try {
+            // Note: The original Spring 4.3.x mock implementation always threw an IllegalStateException when calling the method below.
+            mockActionResponse.sendRedirect(TEST_REDIRECT_URL, TEST_RENDER_URLPARAM_NAME);
+        } catch (Exception ex) {
+            fail("MockActionResponse sendredirect failed.  Exception: " + ex);
+        }
+        assertEquals("Redirect url not as expected ?", TEST_REDIRECT_URL, mockActionResponse.getRedirectedUrl());
+        assertEquals("Redirect url render parameter not as expected ?", TEST_REDIRECT_URL, mockActionResponse.getRenderParameter(TEST_RENDER_URLPARAM_NAME));
+    }
+
+    public void testMockCacheControl() {
+        final int TEST_EXPIRATION_TIME = 30;
+        final String TEST_ETAG = "TEST_ETAG";
+
+        // Call each constructor in sequence, do some basic checks
+        MockCacheControl mockCacheControl = new MockCacheControl();
+        assertEquals("Initial expiry not as expected ?", 0, mockCacheControl.getExpirationTime());
+        assertFalse("Initial scope public ?", mockCacheControl.isPublicScope());
+        assertFalse("Initial use cache true ?", mockCacheControl.useCachedContent());
+        mockCacheControl.setExpirationTime(TEST_EXPIRATION_TIME);
+        assertEquals("Initial expiry not as expected ?", TEST_EXPIRATION_TIME, mockCacheControl.getExpirationTime());
+        mockCacheControl.setPublicScope(true);
+        assertTrue("Updated scope not public ?", mockCacheControl.isPublicScope());
+        mockCacheControl.setPublicScope(false);
+        assertFalse("Updated scope still public ?", mockCacheControl.isPublicScope());
+        mockCacheControl.setETag(TEST_ETAG);
+        assertEquals("ETag not not as expected ?", TEST_ETAG, mockCacheControl.getETag());
+        mockCacheControl.setUseCachedContent(true);
+        assertTrue("Updated use cache not true ?", mockCacheControl.useCachedContent());
+        mockCacheControl.setUseCachedContent(false);
+        assertFalse("Updated use cache still true ?", mockCacheControl.useCachedContent());
+    }
+
+    public void testMockClientDataRequest() {
+        final String TEST_FAKECONTENT = "Some fake content for the test.";
+        final byte[] TEST_FAKECONTENT_ASBYTES = TEST_FAKECONTENT.getBytes(StandardCharsets.UTF_8);
+        final String TEST_CONTENT_TYPE = "text/html";
+        final String TEST_METHOD = "POST";
+
+        // Call each constructor in sequence, do some basic checks
+        MockClientDataRequest mockClientDataRequest = new MockClientDataRequest();  // Only test is to confirm constructor completes without exception
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockClientDataRequest = new MockClientDataRequest(mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockClientDataRequest = new MockClientDataRequest(mockPortalContext, mockPortletContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockClientDataRequest.getPortalContext());
+        mockClientDataRequest.setContent(TEST_FAKECONTENT_ASBYTES);
+        assertEquals("Content length not as expected ?", TEST_FAKECONTENT_ASBYTES.length, mockClientDataRequest.getContentLength());
+        mockClientDataRequest.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        assertEquals("Charset not as expected ?", StandardCharsets.UTF_8.name(), mockClientDataRequest.getCharacterEncoding());
+        mockClientDataRequest.setContentType(TEST_CONTENT_TYPE);
+        assertEquals("Content-type not as expected ?", TEST_CONTENT_TYPE, mockClientDataRequest.getContentType());
+        mockClientDataRequest.setMethod(TEST_METHOD);
+        assertEquals("Method not as expected ?", TEST_METHOD, mockClientDataRequest.getMethod());
+        try ( InputStream inputStream = mockClientDataRequest.getPortletInputStream() ) {
+            final byte[] readContent = new byte[mockClientDataRequest.getContentLength()];
+            inputStream.read(readContent, 0, readContent.length);
+            assertTrue("Read byte array does not match original ?", Arrays.equals(TEST_FAKECONTENT_ASBYTES, readContent));
+        } catch (Exception ex) {
+            fail("MockClientDataRequest read inputstream failed.  Exception: " + ex);
+        }
+        try ( BufferedReader bufferedReader = mockClientDataRequest.getReader() ) {
+            final String theOnlyLine = bufferedReader.readLine();
+            assertEquals("Read line does not match original ?", TEST_FAKECONTENT, theOnlyLine);
+        } catch (Exception ex) {
+            fail("MockClientDataRequest read inputstream failed.  Exception: " + ex);
+        }
+    }
+
+    public void testMockEvent() {
+        final String TEST_EVENT = "TEST_EVENT";
+        final String TEST_EVENT_VALUE = "TEST_EVENT_VALUE";
+        final QName TEST_QNAME = new QName(TEST_EVENT);
+
+        // Call each constructor in sequence, do some basic checks
+        MockEvent mockClientDataRequest = new MockEvent(TEST_EVENT);
+        assertEquals("Initial value not null ?", null, mockClientDataRequest.getValue());
+        assertEquals("Event name does not match ?", TEST_EVENT, mockClientDataRequest.getName());
+        assertEquals("Event QName does not match ?", new QName(TEST_EVENT), mockClientDataRequest.getQName());
+        mockClientDataRequest = new MockEvent(TEST_EVENT, TEST_EVENT_VALUE);
+        assertEquals("Initial value not as expected ?", TEST_EVENT_VALUE, mockClientDataRequest.getValue());
+        assertEquals("Event name does not match ?", TEST_EVENT, mockClientDataRequest.getName());
+        assertEquals("Event QName does not match ?", new QName(TEST_EVENT), mockClientDataRequest.getQName());
+        mockClientDataRequest = new MockEvent(TEST_QNAME);
+        assertEquals("Initial value not null ?", null, mockClientDataRequest.getValue());
+        assertEquals("Event name does not match ?", TEST_EVENT, mockClientDataRequest.getName());
+        assertEquals("Event QName does not match ?", TEST_QNAME, mockClientDataRequest.getQName());
+        mockClientDataRequest = new MockEvent(TEST_QNAME, TEST_EVENT_VALUE);
+        assertEquals("Initial value not as expected ?", TEST_EVENT_VALUE, mockClientDataRequest.getValue());
+        assertEquals("Event name does not match ?", TEST_EVENT, mockClientDataRequest.getName());
+        assertEquals("Event QName does not match ?", TEST_QNAME, mockClientDataRequest.getQName());
+    }
+
+    public void testMockEventRequest() {
+        final MockEvent TEST_MOCKEVENT = new MockEvent("MockEventName", "MockEventValue");
+        final String TEST_METHOD = "POST";
+
+        // Call each constructor in sequence, do some basic checks
+        MockEventRequest mockEventRequest = new MockEventRequest(TEST_MOCKEVENT);
+        assertEquals("Event not the event set ?", TEST_MOCKEVENT, mockEventRequest.getEvent());
+        mockEventRequest.setMethod(TEST_METHOD);
+        assertEquals("Method not the method set ?", TEST_METHOD, mockEventRequest.getMethod());
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockEventRequest = new MockEventRequest(TEST_MOCKEVENT, mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockEventRequest = new MockEventRequest(TEST_MOCKEVENT, mockPortalContext, mockPortletContext);  // Only test is to confirm constructor completes without exception
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockEventRequest.getPortalContext());
+    }
+
+    public void testMockEventResponse() {
+        // Call each constructor in sequence, do some basic checks
+        MockEventResponse mockEventResponse = new MockEventResponse();  // Only test is to confirm constructor completes without exception
+    }
+
+    public void testMockMimeResponse() {;
+        final String TEST_CONTENT_TYPE = "text/html";
+        final int TEST_BUFFERSIZE = 8192;
+        final String TEST_INCLUDED_URL = "localhost:8080/fakeincludedurl";
+        final String TEST_FORWARDED_URL = "localhost:8080/fakeforwardedurl";
+        final String TEST_FAKECONTENT = "Some fake content for the test.";
+        final byte[] TEST_FAKECONTENT_ASBYTES = TEST_FAKECONTENT.getBytes(StandardCharsets.UTF_8);
+
+        // Call each constructor in sequence, do some basic checks
+        MockMimeResponse mockMimeResponse = new MockMimeResponse();
+        assertEquals("Default character encoding not as expected ?", WebUtils.DEFAULT_CHARACTER_ENCODING, mockMimeResponse.getCharacterEncoding());
+        assertEquals("Default locale not as expected ?", Locale.getDefault(), mockMimeResponse.getLocale());
+        assertTrue("Default buffersize not as expected ?", mockMimeResponse.getBufferSize() >= 4096);
+        assertFalse("Mock response already committed ?", mockMimeResponse.isCommitted());
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortletRequest mockPortletRequest = new MockPortletRequest(mockPortalContext, mockPortletContext);
+        mockMimeResponse = new MockMimeResponse(mockPortalContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockMimeResponse.getPortalContext());
+        mockMimeResponse = new MockMimeResponse(mockPortalContext, mockPortletRequest);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockMimeResponse.getPortalContext());
+        mockMimeResponse.setContentType(TEST_CONTENT_TYPE);
+        assertEquals("Content-type not as expected ?", TEST_CONTENT_TYPE, mockMimeResponse.getContentType());
+        mockMimeResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        assertEquals("Charset not as expected ?", StandardCharsets.UTF_8.name(), mockMimeResponse.getCharacterEncoding());
+        mockMimeResponse.setBufferSize(TEST_BUFFERSIZE);
+        assertEquals("Buffersize not as expected ?", TEST_BUFFERSIZE, mockMimeResponse.getBufferSize());
+        mockMimeResponse.setCommitted(true);
+        assertTrue("Response not committed ?", mockMimeResponse.isCommitted());
+        mockMimeResponse.setCommitted(false);
+        assertFalse("Response still committed ?", mockMimeResponse.isCommitted());
+        mockMimeResponse.setIncludedUrl(TEST_INCLUDED_URL);
+        assertEquals("Included URL not as expected ?", TEST_INCLUDED_URL, mockMimeResponse.getIncludedUrl());
+        mockMimeResponse.setForwardedUrl(TEST_FORWARDED_URL);
+        assertEquals("Forwarded URL not as expected ?", TEST_FORWARDED_URL, mockMimeResponse.getForwardedUrl());
+        try {
+            assertNotNull("OutputStream null ?", mockMimeResponse.getPortletOutputStream());
+        } catch (Exception ex) {
+            fail("MockMimeResponse get outputstream failed.  Exception: " + ex);
+        }
+        PortletURL portletUrl = mockMimeResponse.createRenderURL();
+        assertNotNull("RenderURL null ?", portletUrl);
+        portletUrl = mockMimeResponse.createActionURL();
+        assertNotNull("ActionURL null ?", portletUrl);
+        ResourceURL resourceURL = mockMimeResponse.createResourceURL();
+        assertNotNull("ResourceURL null ?", resourceURL);
+        assertNotNull("CacheControl null ?", mockMimeResponse.getCacheControl());
+
+        try {
+            OutputStream outputStream = mockMimeResponse.getPortletOutputStream();
+            assertNotNull("OutputStream null ?", outputStream);
+            outputStream.write(TEST_FAKECONTENT_ASBYTES);
+            final byte[] writeResult = mockMimeResponse.getContentAsByteArray();
+            assertTrue("Written byte array does not match original ?", Arrays.equals(TEST_FAKECONTENT_ASBYTES, writeResult));
+            assertTrue("Buffer not committed after processing ?", mockMimeResponse.isCommitted());
+        } catch (Exception ex) {
+            fail("MockMimeResponse get/process outputstream failed.  Exception: " + ex);
+        }
+        try {
+            mockMimeResponse.resetBuffer();
+        } catch (IllegalStateException ise) {
+            // Expected failure
+        }
+
+        mockMimeResponse.setCommitted(false);
+        mockMimeResponse.resetBuffer();
+        try {
+            PrintWriter printWriter = mockMimeResponse.getWriter();
+            assertNotNull("PrintWriter null ?", printWriter);
+            printWriter.print(TEST_FAKECONTENT);
+            mockMimeResponse.flushBuffer();
+            final byte[] writeResult = mockMimeResponse.getContentAsByteArray();
+            assertTrue("Written byte array does not match original ?", Arrays.equals(TEST_FAKECONTENT_ASBYTES, writeResult));
+            assertTrue("Buffer not committed after processing ?", mockMimeResponse.isCommitted());
+            final String writeResultAsString = mockMimeResponse.getContentAsString();
+            assertEquals("Written result does not match original ?", TEST_FAKECONTENT, writeResultAsString);
+        } catch (Exception ex) {
+            fail("MockMimeResponse get/process printwriter failed.  Exception: " + ex);
+        }
+        try {
+            mockMimeResponse.reset();
+        } catch (IllegalStateException ise) {
+            // Expected failure
+        }
+
+        mockMimeResponse.setCommitted(false);
+        mockMimeResponse.reset();
+        assertNull("After reset, character encoding not null ?", mockMimeResponse.getCharacterEncoding());
+        assertNull("After reset, content-type not null ?", mockMimeResponse.getContentType());
+        assertNull("After reset, locale not null ?", mockMimeResponse.getLocale());
+    }
+
+    public void testMockMultipartActionRequest() {
+        final String TEST_FILENAME = "TEST_TEMPFILE";
+        final String TEST_FILENAME2 = "TEST_TEMPFILE2";
+        final String TEST_CONTENT_TYPE = "text/html";
+        final String TEST_FAKECONTENT = "Some fake content for the test.";
+        final byte[] TEST_FAKECONTENT_ASBYTES = TEST_FAKECONTENT.getBytes(StandardCharsets.UTF_8);
+
+        // Call each constructor in sequence, do some basic checks
+        MockMultipartActionRequest mockMultipartActionRequest = new MockMultipartActionRequest();
+        assertFalse("Initial multipartrequest has file names ?", mockMultipartActionRequest.getFileNames().hasNext());
+        assertTrue("Initial multipartrequest has nonempty map ?", mockMultipartActionRequest.getFileMap().isEmpty());
+        assertTrue("Initial multipartrequest has nonempty multivalue map ?", mockMultipartActionRequest.getMultiFileMap().isEmpty());
+        final MultipartFile multipartFile = new TestMockMultipartFile(TEST_FILENAME, TEST_FILENAME, TEST_CONTENT_TYPE, TEST_FAKECONTENT_ASBYTES);
+        final MultipartFile multipartFile2 = new TestMockMultipartFile(TEST_FILENAME2, TEST_FILENAME2, TEST_CONTENT_TYPE, TEST_FAKECONTENT_ASBYTES);
+        mockMultipartActionRequest.addFile(multipartFile);
+        assertTrue("Multipartrequest does not have file names ?", mockMultipartActionRequest.getFileNames().hasNext());
+        assertFalse("Multipartrequest does not have nonempty map ?", mockMultipartActionRequest.getFileMap().isEmpty());
+        assertFalse("Multipartrequest does not have nonempty multivalue map ?", mockMultipartActionRequest.getMultiFileMap().isEmpty());
+        mockMultipartActionRequest.addFile(multipartFile2);
+        assertTrue("Multipartrequest does not have file names ?", mockMultipartActionRequest.getFileNames().hasNext());
+        assertFalse("Multipartrequest does not have nonempty map ?", mockMultipartActionRequest.getFileMap().isEmpty());
+        assertFalse("Multipartrequest does not have nonempty multivalue map ?", mockMultipartActionRequest.getMultiFileMap().isEmpty());
+        assertEquals("Retrieved file 1 not as expected ?", multipartFile, mockMultipartActionRequest.getFile(TEST_FILENAME));
+        assertEquals("Retrieved file 1 content-type not as expected ?", TEST_CONTENT_TYPE, mockMultipartActionRequest.getMultipartContentType(TEST_FILENAME));
+        assertEquals("Retrieved file 2 not as expected ?", multipartFile2, mockMultipartActionRequest.getFile(TEST_FILENAME2));
+        assertEquals("Retrieved file 2 content-type not as expected ?", TEST_CONTENT_TYPE, mockMultipartActionRequest.getMultipartContentType(TEST_FILENAME2));
+        final Map<String, MultipartFile> fileMap = mockMultipartActionRequest.getFileMap();
+        assertNotNull("File map is null ?", fileMap);
+        assertTrue("File 1 name not in map ?", fileMap.containsKey(TEST_FILENAME));
+        assertTrue("File 2 name not in map ?", fileMap.containsKey(TEST_FILENAME2));
+        assertTrue("File 1 not in map ?", fileMap.containsValue(multipartFile));
+        assertTrue("File 2 not in map ?", fileMap.containsValue(multipartFile2));
+        MultiValueMap<String, MultipartFile> fileMultiValueMap = mockMultipartActionRequest.getMultiFileMap();
+        assertNotNull("MultiValue file map is null ?", fileMultiValueMap);
+        MultipartFile retrievedFile = fileMultiValueMap.getFirst(TEST_FILENAME);
+        assertNotNull("File 1 1st value is null ?", retrievedFile);
+        assertEquals("File 1 not in multivalue map ?", multipartFile, retrievedFile);
+        retrievedFile = fileMultiValueMap.getFirst(TEST_FILENAME2);
+        assertNotNull("File 2 1st value is null ?", retrievedFile);
+        assertEquals("File 2 not in multivalue map ?", multipartFile2, retrievedFile);
+    }
+
+    public void testMockPortalContext() {
+        final String TEST_PROPERTY = "TEST_PROPERTY";
+        final String TEST_PROPERTY_VALUE = "Property_Value_1";
+        final String TEST_PROPERTY2 = "TEST_PROPERTY2";
+        final String TEST_PROPERTY2_VALUE = "Property_Value_2";
+
+        // Call each constructor in sequence, do some basic checks
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        assertNotNull("MockPortalContext portalInfo null ?", mockPortalContext.getPortalInfo());
+        assertNotNull("MockPortalContext property names null ?", mockPortalContext.getPropertyNames());
+        assertFalse("MockPortalConext initial propertyNames not empty ?", mockPortalContext.getPropertyNames().hasMoreElements());
+        final Enumeration<PortletMode> supportedPortletModes = mockPortalContext.getSupportedPortletModes();
+        assertNotNull("MockPortalContext supported portlet modes null ?", supportedPortletModes);
+        final ArrayList<PortletMode> portletModeList = new ArrayList<>();
+        while (supportedPortletModes.hasMoreElements()) {
+            portletModeList.add(supportedPortletModes.nextElement());
+        }
+        assertEquals("MockPortalContext does not support three modes ?", 3, portletModeList.size());
+        assertTrue("MockPortalContext does not contain PortletMode.VIEW ?", portletModeList.contains(PortletMode.VIEW));
+        assertTrue("MockPortalContext does not contain PortletMode.EDIT ?", portletModeList.contains(PortletMode.EDIT));
+        assertTrue("MockPortalContext does not contain PortletMode.HELP ?", portletModeList.contains(PortletMode.HELP));
+
+        final Enumeration<WindowState> supportedWindowStates = mockPortalContext.getSupportedWindowStates();
+        assertNotNull("MockPortalContext supported window modes null ?", supportedWindowStates);
+        final ArrayList<WindowState> windowStateList = new ArrayList<>();
+        while (supportedWindowStates.hasMoreElements()) {
+            windowStateList.add(supportedWindowStates.nextElement());
+        }
+        assertEquals("MockPortalContext does not support three states ?", 3, windowStateList.size());
+        assertTrue("MockPortalContext does not contain WindowState.NORMAL ?", windowStateList.contains(WindowState.NORMAL));
+        assertTrue("MockPortalContext does not contain WindowState.MAXIMIZED ?", windowStateList.contains(WindowState.MAXIMIZED));
+        assertTrue("MockPortalContext does not contain WindowState.MINIMIZED ?", windowStateList.contains(WindowState.MINIMIZED));
+
+        mockPortalContext.setProperty(TEST_PROPERTY, TEST_PROPERTY_VALUE);
+        mockPortalContext.setProperty(TEST_PROPERTY2, TEST_PROPERTY2_VALUE);
+        final Enumeration<String> propertyNames = mockPortalContext.getPropertyNames();
+        assertNotNull("MockPortalContext property names null ?", propertyNames);
+        final ArrayList<String> propertyNamesList = new ArrayList<>();
+        while (propertyNames.hasMoreElements()) {
+            propertyNamesList.add(propertyNames.nextElement());
+        }
+        assertEquals("MockPortalContext does not contain two properties ?", 2, propertyNamesList.size());
+        assertTrue("MockPortalContext does not contain property name " + TEST_PROPERTY + " ?", propertyNamesList.contains(TEST_PROPERTY));
+        assertTrue("MockPortalContext does not contain property name " + TEST_PROPERTY2 + " ?", propertyNamesList.contains(TEST_PROPERTY2));
+        assertEquals("MockPortalContext value of property name " + TEST_PROPERTY + " not as expected ?", TEST_PROPERTY_VALUE, mockPortalContext.getProperty(TEST_PROPERTY));
+        assertEquals("MockPortalContext value of property name " + TEST_PROPERTY2 + " not as expected ?", TEST_PROPERTY2_VALUE, mockPortalContext.getProperty(TEST_PROPERTY2));
+    }
+
+    public void testMockPortletConfig() {
+        final String TEST_PORTLETNAME = "TestPortletName";
+        final String TEST_INITPARAM = "TEST_INITPARAM";
+        final String TEST_INITPARAM_VALUE = "Init_Value_1";
+        final String TEST_INITPARAM2= "TEST_INITPARAM2";
+        final String TEST_INITPARAM_VALUE2 = "Init_Value_2";
+        final String TEST_RENDERPARAM_NAME = "Test_RenderParam_1";
+        final String TEST_RENDERPARAM_NAME2 = "Test_RenderParam_2";
+        final String TEST_DEFAULTNAMESPACE = "Test_Default_Namespace";
+        final QName TEST_QNAME = new QName(TEST_PORTLETNAME);
+        final QName TEST_QNAME2 = new QName(TEST_DEFAULTNAMESPACE);
+        final String TEST_RUNTIME_OPTION = "TEST_RUNTIME_OPTION";
+        final String TEST_RUNTIME_OPTION2 = "TEST_RUNTIME_OPTION2";
+        final String TEST_RUNTIME_VALUE1 = "Test_Runtime_Option1";
+        final String TEST_RUNTIME_VALUE2 = "Test_Runtime_Option2";
+
+        // Call each constructor in sequence, do some basic checks
+        MockPortletConfig mockPortletConfig = new MockPortletConfig();
+        assertNotNull("MockPortletConfig default PortletContext is null ?", mockPortletConfig.getPortletContext());
+        assertEquals("MockPortletConfig default name not empty string ?", "", mockPortletConfig.getPortletName());
+        mockPortletConfig = new MockPortletConfig(TEST_PORTLETNAME);
+        assertNotNull("MockPortletConfig default PortletContext is null ?", mockPortletConfig.getPortletContext());
+        assertEquals("MockPortletConfig name not constructor set value ?", TEST_PORTLETNAME, mockPortletConfig.getPortletName());
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        mockPortletConfig = new MockPortletConfig(mockPortletContext, TEST_PORTLETNAME);
+        assertEquals("MockPortletConfig PortletContext constructor set value ?", mockPortletContext, mockPortletConfig.getPortletContext());
+        assertEquals("MockPortletConfig name not constructor set value ?", TEST_PORTLETNAME, mockPortletConfig.getPortletName());
+        assertNull("MockPortletConfig default locale resource bundle not null ?", mockPortletConfig.getResourceBundle(Locale.getDefault()));
+        final ResourceBundle resourceBundle = new TetMockResourceBundle();
+        mockPortletConfig.setResourceBundle(Locale.getDefault(), resourceBundle);
+        assertEquals("Default locale resource bundle not set value ?", resourceBundle, mockPortletConfig.getResourceBundle(Locale.getDefault()));
+
+        assertNull(TEST_INITPARAM + " present before set ?", mockPortletConfig.getInitParameter(TEST_INITPARAM));
+        assertNull(TEST_INITPARAM2 + " present before set ?", mockPortletConfig.getInitParameter(TEST_INITPARAM));
+        mockPortletConfig.addInitParameter(TEST_INITPARAM, TEST_INITPARAM_VALUE);
+        mockPortletConfig.addInitParameter(TEST_INITPARAM2, TEST_INITPARAM_VALUE2);
+        assertEquals("Retrieved init parameter 1 does not match set value ?", TEST_INITPARAM_VALUE, mockPortletConfig.getInitParameter(TEST_INITPARAM));
+        assertEquals("Retrieved init parameter 2 does not match set value ?", TEST_INITPARAM_VALUE2, mockPortletConfig.getInitParameter(TEST_INITPARAM2));
+        final Enumeration<String> initParameterNames = mockPortletConfig.getInitParameterNames();
+        int initParameterNameCount = 0;
+        assertNotNull("InitParameter names null after additions ?", initParameterNames);
+        assertTrue("InitParameter names empty after additions ?", initParameterNames.hasMoreElements());
+        while (initParameterNames.hasMoreElements()) {
+            initParameterNameCount++;
+            String currentInitParameterName = initParameterNames.nextElement();
+            assertTrue("Initparam name not one of two expected matches ?", TEST_INITPARAM.equals(currentInitParameterName) || 
+                    TEST_INITPARAM2.equals(currentInitParameterName));
+        }
+        assertEquals("InitParameter names size not 2 ?", 2, initParameterNameCount);
+
+        assertFalse("Initial renderparam names not empty ?", mockPortletConfig.getPublicRenderParameterNames().hasMoreElements());
+        mockPortletConfig.addPublicRenderParameterName(TEST_RENDERPARAM_NAME);
+        mockPortletConfig.addPublicRenderParameterName(TEST_RENDERPARAM_NAME2);
+        final Enumeration<String> publicRenderParameterNames = mockPortletConfig.getPublicRenderParameterNames();
+        int publicRenderParameterNameCount = 0;
+        assertNotNull("PublicRenderParameter names null after additions ?", publicRenderParameterNames);
+        assertTrue("PublicRenderParameter names empty after additions ?", publicRenderParameterNames.hasMoreElements());
+        while (publicRenderParameterNames.hasMoreElements()) {
+            publicRenderParameterNameCount++;
+            String currentPublicRenderParameterName = publicRenderParameterNames.nextElement();
+            assertTrue("PublicRenderParameter name not one of two expected matches ?", TEST_RENDERPARAM_NAME.equals(currentPublicRenderParameterName) || 
+                    TEST_RENDERPARAM_NAME2.equals(currentPublicRenderParameterName));
+        }
+        assertEquals("PublicRenderParameter names size not 2 ?", 2, publicRenderParameterNameCount);
+        mockPortletConfig.setDefaultNamespace(TEST_DEFAULTNAMESPACE);
+        assertEquals("Default namespace not set value ?", TEST_DEFAULTNAMESPACE, mockPortletConfig.getDefaultNamespace());
+
+        assertFalse("Initial publishing event QNames not empty ?", mockPortletConfig.getPublishingEventQNames().hasMoreElements());
+        assertFalse("Initial processing event QNames not empty ?", mockPortletConfig.getProcessingEventQNames().hasMoreElements());
+        mockPortletConfig.addPublishingEventQName(TEST_QNAME);
+        mockPortletConfig.addProcessingEventQName(TEST_QNAME2);
+        final Enumeration<QName> publishingEventQNames = mockPortletConfig.getPublishingEventQNames();
+        assertNotNull("PublishingEventQNames names null after additions ?", publishingEventQNames);
+        assertTrue("PublishingEventQNames names empty after additions ?", publishingEventQNames.hasMoreElements());
+        final QName firstPublishingEventQName = publishingEventQNames.nextElement();
+        assertEquals("First publishing event QName not set value ?", TEST_QNAME, firstPublishingEventQName);
+        final Enumeration<QName> processingEventQNames = mockPortletConfig.getProcessingEventQNames();
+        assertNotNull("ProcessingEventQNames names null after additions ?", processingEventQNames);
+        assertTrue("ProcessingEventQNames names empty after additions ?", processingEventQNames.hasMoreElements());
+        final QName firstProcessingEventQName = processingEventQNames.nextElement();
+        assertEquals("First processing event QName not set value ?", TEST_QNAME2, firstProcessingEventQName);
+
+        assertFalse("Initial supported locales not empty ?", mockPortletConfig.getSupportedLocales().hasMoreElements());
+        mockPortletConfig.addSupportedLocale(Locale.getDefault());
+        final Enumeration<Locale> supportedLocales = mockPortletConfig.getSupportedLocales();
+        assertNotNull("Supported locales null after additions ?", supportedLocales);
+        assertTrue("Supported locales names empty after additions ?", supportedLocales.hasMoreElements());
+        final Locale firstSupportedLocale = supportedLocales.nextElement();
+        assertEquals("First supported locale not set value ?", Locale.getDefault(), firstSupportedLocale);
+
+        assertTrue("Portlet runtime options not empty before set ?", mockPortletConfig.getContainerRuntimeOptions().isEmpty());
+        mockPortletConfig.addContainerRuntimeOption(TEST_RUNTIME_OPTION, TEST_RUNTIME_VALUE1);
+        mockPortletConfig.addContainerRuntimeOption(TEST_RUNTIME_OPTION2, TEST_RUNTIME_VALUE2);
+        final Map<String, String[]>  containerRuntimeOptions = mockPortletConfig.getContainerRuntimeOptions();
+        assertNotNull("Runtime options null after additions ?", containerRuntimeOptions);
+        assertFalse("Runtime options empty after additions ?", containerRuntimeOptions.isEmpty());
+        assertTrue("Runtime option 1 not present after setting ?", containerRuntimeOptions.containsKey(TEST_RUNTIME_OPTION));
+        assertTrue("Runtime option 2 not present after setting ?", containerRuntimeOptions.containsKey(TEST_RUNTIME_OPTION2));
+        assertEquals("Runtime option 1 not equal to set value ?", TEST_RUNTIME_VALUE1, containerRuntimeOptions.get(TEST_RUNTIME_OPTION)[0]);
+        assertEquals("Runtime option 2 not equal to set value ?", TEST_RUNTIME_VALUE2, containerRuntimeOptions.get(TEST_RUNTIME_OPTION2)[0]);
+    }
+
+    public void testMockPortletContext() {
+        final String TEST_ATTRIBUTE = "TEST_ATTRIBUTE";
+        final String TEST_ATTRIBUTE_VALUE = "Attribute_Value_1";
+        final String TEST_ATTRIBUTE2 = "TEST_ATTRIBUTE2";
+        final String TEST_ATTRIBUTE_VALUE2 = "Attribute_Value_2";
+        final String TEST_INITPARAM = "TEST_INITPARAM";
+        final String TEST_INITPARAM_VALUE = "Init_Value_1";
+        final String TEST_INITPARAM2 = "TEST_INITPARAM2";
+        final String TEST_INITPARAM_VALUE2 = "Init_Value_2";
+        final String TEST_CONTEXT_NAME = "TEST_CONTEXT_NAME";
+        final String TEST_RUNTIME_OPTION = "TEST_RUNTIME_OPTION";
+        final String TEST_RUNTIME_OPTION2 = "TEST_RUNTIME_OPTION2";
+
+        // Call each constructor in sequence, do some basic checks
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        assertNotNull("MockPortletContext serverInfo null ?", mockPortletContext.getServerInfo());
+        try {
+            assertNotNull("MockPortletContext PortletRequestDispatcher null ?", mockPortletContext.getRequestDispatcher("IllegalPrefix"));
+            fail("PortletRequestDispatcher path must start with /");
+        } catch (IllegalArgumentException iae) {
+            // Expected exception
+        }
+        assertNotNull("MockPortletContext PortletRequestDispatcher for / null ?", mockPortletContext.getRequestDispatcher("/"));
+        assertNull("MockPortletContext named PortletRequestDispatcher not null ?", mockPortletContext.getNamedDispatcher("SomeName"));
+        assertNotNull("MockPortletContext resource stream for / null ?", mockPortletContext.getResourceAsStream("/"));
+        assertNull("MockPortletContext resource stream for /ThisDoesNotExist not null ?", mockPortletContext.getResourceAsStream("/ThisDoesNotExist"));
+        assertTrue("MockPortletContext major version not >= 2 ?", mockPortletContext.getMajorVersion() >= 2);
+        assertTrue("MockPortletContext minor version not >= 0 ?", mockPortletContext.getMajorVersion() >= 0);
+        assertNull("MockPortletContext MIME type for / not null ?", mockPortletContext.getMimeType("/"));
+        assertNotNull("MockPortletContext real path for / null ?", mockPortletContext.getRealPath("/"));
+        assertNull("MockPortletContext real path for /ThisDoesNotExist not null ? ?", mockPortletContext.getRealPath("/ThisDoesNotExist"));
+        assertNotNull("MockPortletContext resource paths for / null ?", mockPortletContext.getResourcePaths("/"));
+        assertNull("MockPortletContext resource paths for / null ?", mockPortletContext.getResourcePaths("/ThisDoesNotExist"));
+        try {
+            assertNotNull("MockPortletContext resource URL for / null ?", mockPortletContext.getResource("/"));
+        } catch (MalformedURLException mue) {
+            fail("MockPortletContext resource URL for / failed.  Exception: " + mue);
+        }
+        try {
+            assertNull("MockPortletContext resource URL for /ThisDoesNotExist not null ?", mockPortletContext.getResource("/ThisDoesNotExist"));
+        } catch (MalformedURLException mue) {
+            fail("MockPortletContext resource URL for /ThisDoesNotExist failed.  Exception: " + mue);
+        }
+
+        assertNull(TEST_ATTRIBUTE + " present before set ?", mockPortletContext.getAttribute(TEST_ATTRIBUTE));
+        assertNull(TEST_ATTRIBUTE2 + " present before set ?", mockPortletContext.getAttribute(TEST_ATTRIBUTE2));
+        mockPortletContext.setAttribute(TEST_ATTRIBUTE, TEST_ATTRIBUTE_VALUE);
+        mockPortletContext.setAttribute(TEST_ATTRIBUTE2, TEST_ATTRIBUTE_VALUE2);
+        assertEquals("Retrieved atribute 1 does not match set value ?", TEST_ATTRIBUTE_VALUE, mockPortletContext.getAttribute(TEST_ATTRIBUTE));
+        assertEquals("Retrieved atribute 2 does not match set value ?", TEST_ATTRIBUTE_VALUE2, mockPortletContext.getAttribute(TEST_ATTRIBUTE2));
+        final Enumeration<String> attributeNames = mockPortletContext.getAttributeNames();
+        int attributeNameCount = 0;
+        assertNotNull("Attribute names null after additions ?", attributeNames);
+        assertTrue("Attribute names empty after additions ?", attributeNames.hasMoreElements());
+        while (attributeNames.hasMoreElements()) {
+            attributeNameCount++;
+            String currentAttibuteName = attributeNames.nextElement();
+            assertTrue("Attribute name not one of two or three expected matches ?", TEST_ATTRIBUTE.equals(currentAttibuteName) || 
+                    TEST_ATTRIBUTE2.equals(currentAttibuteName) ||
+                    WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE.equals(currentAttibuteName));
+        }
+        assertTrue("Atribute names size not 2 or 3 ?", attributeNameCount >= 2 && attributeNameCount <= 3);
+
+        assertNull(TEST_INITPARAM + " present before set ?", mockPortletContext.getInitParameter(TEST_INITPARAM));
+        assertNull(TEST_INITPARAM2 + " present before set ?", mockPortletContext.getInitParameter(TEST_INITPARAM));
+        mockPortletContext.addInitParameter(TEST_INITPARAM, TEST_INITPARAM_VALUE);
+        mockPortletContext.addInitParameter(TEST_INITPARAM2, TEST_INITPARAM_VALUE2);
+        assertEquals("Retrieved init parameter 1 does not match set value ?", TEST_INITPARAM_VALUE, mockPortletContext.getInitParameter(TEST_INITPARAM));
+        assertEquals("Retrieved init parameter 2 does not match set value ?", TEST_INITPARAM_VALUE2, mockPortletContext.getInitParameter(TEST_INITPARAM2));
+        final Enumeration<String> initParameterNames = mockPortletContext.getInitParameterNames();
+        int initParameterNameCount = 0;
+        assertNotNull("InitParameter names null after additions ?", initParameterNames);
+        assertTrue("InitParameter names empty after additions ?", initParameterNames.hasMoreElements());
+        while (initParameterNames.hasMoreElements()) {
+            initParameterNameCount++;
+            String currentInitParameterName = initParameterNames.nextElement();
+            assertTrue("Initparam name not one of two expected matches ?", TEST_INITPARAM.equals(currentInitParameterName) || 
+                    TEST_INITPARAM2.equals(currentInitParameterName));
+        }
+        assertEquals("InitParameter names size not 2 ?", 2, initParameterNameCount);
+
+        mockPortletContext.log("Test logging call");
+        mockPortletContext.log("Test logging call", new Exception("Fake Exception"));
+
+        assertEquals("Default portlet context name not as expected ?", "MockPortletContext", mockPortletContext.getPortletContextName());
+        mockPortletContext.setPortletContextName(TEST_CONTEXT_NAME);
+        assertEquals("Portlet context name not equal to set value ?", TEST_CONTEXT_NAME, mockPortletContext.getPortletContextName());
+
+        assertFalse("Portlet runtime options not empty before set ?", mockPortletContext.getContainerRuntimeOptions().hasMoreElements());
+        mockPortletContext.addContainerRuntimeOption(TEST_RUNTIME_OPTION);
+        mockPortletContext.addContainerRuntimeOption(TEST_RUNTIME_OPTION2);
+        final Enumeration<String> containerRuntimeOptions = mockPortletContext.getContainerRuntimeOptions();
+        int containerRuntimeOptionsCount = 0;
+        assertNotNull("Runtime options null after additions ?", containerRuntimeOptions);
+        assertTrue("Runtime options empty after additions ?", containerRuntimeOptions.hasMoreElements());
+        while (containerRuntimeOptions.hasMoreElements()) {
+            containerRuntimeOptionsCount++;
+            String currentRuntimeOption = containerRuntimeOptions.nextElement();
+            assertTrue("Runtime option not one of two expected matches ?", TEST_RUNTIME_OPTION.equals(currentRuntimeOption) || 
+                    TEST_RUNTIME_OPTION2.equals(currentRuntimeOption));
+        }
+        assertEquals("Runtime options size not 2 ?", 2, containerRuntimeOptionsCount);
+    }
+
+    public void testMockPortletPreferences() {
+        final String PORTLET_READONLY = "PORTLET_READONLY";
+        final String PORTLET_READONLY_VALUE = "Readonly_Value_1";
+        final String PORTLET_READONLY2 = "PORTLET_READONLY2";
+        final String PORTLET_READONLY2_VALUE = "Readonly_Value_2";
+        final String PORTLET_WRITEABLE = "PORTLET_WRITEABLE";
+        final String PORTLET_WRITEABLE_VALUE = "Writeable_Value_1";
+        final String PORTLET_WRITEABLE2 = "PORTLET_WRITEABLE2";
+        final String PORTLET_WRITEABLE2_VALUE = "Writeable_Value_2";
+        final String PORTLET_WRITEABLE_ARRAY = "PORTLET_WRITEABLE_ARRAY";
+        final String[] PORTLET_WRITEABLE_ARRAY_VALUE = { "Writeable_Array_Value_1", "Writeable_Array_Value" };
+
+        // Call each constructor in sequence, do some basic checks
+        final MockPortletPreferences mockPortletPreferences = new MockPortletPreferences();
+        assertFalse("MockPortletPreferences initial preferences names non-empty ?", mockPortletPreferences.getNames().hasMoreElements());
+        assertTrue("MockPortletPreferences initial preferences non-empty ?", mockPortletPreferences.getMap().isEmpty());
+
+        // Test set readonly permitted before key exists
+        assertFalse(PORTLET_READONLY + " readonly before set ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY));
+        assertFalse(PORTLET_READONLY2 + " readonly before set ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY2));
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY, true);
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY2, true);
+        assertTrue(PORTLET_READONLY + " not readonly after set ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY));
+        assertTrue(PORTLET_READONLY2 + " not readonly after set ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY2));
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY, false);
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY2, false);
+        assertFalse(PORTLET_READONLY + " readonly after clear ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY));
+        assertFalse(PORTLET_READONLY2 + " readonly after clear ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY2));
+
+        try {
+            // Test initial set and replace
+            mockPortletPreferences.setValue(PORTLET_READONLY, PORTLET_READONLY_VALUE);
+            assertEquals("Set value mismatch ?", PORTLET_READONLY_VALUE, mockPortletPreferences.getValue(PORTLET_READONLY, null));
+            mockPortletPreferences.setValue(PORTLET_READONLY, PORTLET_READONLY_VALUE);
+            assertEquals("Set value mismatch ?", PORTLET_READONLY_VALUE, mockPortletPreferences.getValue(PORTLET_READONLY, null));
+        } catch (Exception ex) {
+            fail("Setvalue failed unexpectedly: " + ex);
+        }
+        try {
+            // Test initial set and replace
+            mockPortletPreferences.setValue(PORTLET_READONLY2, PORTLET_READONLY2_VALUE);
+            assertEquals("Set value mismatch ?", PORTLET_READONLY2_VALUE, mockPortletPreferences.getValue(PORTLET_READONLY2, null));
+            mockPortletPreferences.setValue(PORTLET_READONLY2, PORTLET_READONLY2_VALUE);
+            assertEquals("Set value mismatch ?", PORTLET_READONLY2_VALUE, mockPortletPreferences.getValue(PORTLET_READONLY2, null));
+        } catch (Exception ex) {
+            fail("Setvalue failed unexpectedly: " + ex);
+        }
+
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY, true);
+        mockPortletPreferences.setReadOnly(PORTLET_READONLY2, true);
+        try {
+            // Test set of readonly value fails
+            mockPortletPreferences.setValue(PORTLET_READONLY, PORTLET_READONLY_VALUE);
+            fail("Setvalue worked unexpectedly for readonly value");
+        } catch (Exception ex) {
+            assertTrue("Unexpected exception type failure ?  Ex: " + ex, ex instanceof ReadOnlyException);
+            assertTrue(PORTLET_READONLY + " is not readonly ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY));
+        }
+        try {
+            // Test reset of readonly value fails
+            mockPortletPreferences.reset(PORTLET_READONLY);
+            fail("Reset worked unexpectedly for readonly value");
+        } catch (Exception ex) {
+            assertTrue("Unexpected exception type failure ?  Ex: " + ex, ex instanceof ReadOnlyException);
+            assertTrue(PORTLET_READONLY + " is not readonly ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY));
+        }
+        try {
+            // Test set of readonly value fails
+            mockPortletPreferences.setValue(PORTLET_READONLY2, PORTLET_READONLY2_VALUE);
+            fail("Setvalue worked unexpectedly for readonly value");
+        } catch (Exception ex) {
+            assertTrue("Unexpected exception type failure ?  Ex: " + ex, ex instanceof ReadOnlyException);
+            assertTrue(PORTLET_READONLY2 + " is not readonly ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY2));
+        }
+        try {
+            // Test reset of readonly value fails
+            mockPortletPreferences.reset(PORTLET_READONLY2);
+            fail("Reset worked unexpectedly for readonly value");
+        } catch (Exception ex) {
+            assertTrue("Unexpected exception type failure ?  Ex: " + ex, ex instanceof ReadOnlyException);
+            assertTrue(PORTLET_READONLY2 + " is not readonly ?", mockPortletPreferences.isReadOnly(PORTLET_READONLY2));
+        }
+
+        try {
+            // Test initial set, get and reset
+            mockPortletPreferences.setValue(PORTLET_WRITEABLE, PORTLET_WRITEABLE_VALUE);
+            mockPortletPreferences.setValue(PORTLET_WRITEABLE2, PORTLET_WRITEABLE2_VALUE);
+            assertEquals("Set value mismatch ?", PORTLET_WRITEABLE_VALUE, mockPortletPreferences.getValue(PORTLET_WRITEABLE, null));
+            assertEquals("Set value mismatch ?", PORTLET_WRITEABLE2_VALUE, mockPortletPreferences.getValue(PORTLET_WRITEABLE2, null));
+            assertFalse(PORTLET_WRITEABLE + " is readonly ?", mockPortletPreferences.isReadOnly(PORTLET_WRITEABLE));
+            assertFalse(PORTLET_WRITEABLE2 + " is readonly ?", mockPortletPreferences.isReadOnly(PORTLET_WRITEABLE2));
+            mockPortletPreferences.reset(PORTLET_WRITEABLE);
+            mockPortletPreferences.reset(PORTLET_WRITEABLE2);
+            assertEquals("After reset getValue with null not null ?", null, mockPortletPreferences.getValue(PORTLET_WRITEABLE, null));
+            assertEquals("After reset getValue with null not null ?", null, mockPortletPreferences.getValue(PORTLET_WRITEABLE2, null));
+        } catch (Exception ex) {
+            fail("Set/Get/Reset failed unexpectedly: " + ex);
+        }
+
+        try {
+            // Test initial array set, get, names and map
+            mockPortletPreferences.setValues(PORTLET_WRITEABLE_ARRAY, PORTLET_WRITEABLE_ARRAY_VALUE);
+
+            assertEquals("Set values mismatch ?", PORTLET_WRITEABLE_ARRAY_VALUE, mockPortletPreferences.getValues(PORTLET_WRITEABLE_ARRAY, null));
+            assertFalse(PORTLET_WRITEABLE_ARRAY + " is readonly ?", mockPortletPreferences.isReadOnly(PORTLET_WRITEABLE_ARRAY));
+
+            // There should be two readonly attributes plus the array attribute present at this point
+            final Enumeration<String> preferencesNames = mockPortletPreferences.getNames();
+            assertNotNull("Preferences names null after additions ?", preferencesNames);
+            assertTrue("Preferences names empty after additions ?", preferencesNames.hasMoreElements());
+            int preferencesNameCount = 0;
+            while (preferencesNames.hasMoreElements()) {
+                preferencesNameCount++;
+                String currentPreferencesName = preferencesNames.nextElement();
+                assertTrue("Preferences name not one of three expected matches ?", PORTLET_WRITEABLE_ARRAY.equals(currentPreferencesName) || 
+                        PORTLET_READONLY.equals(currentPreferencesName) ||
+                        PORTLET_READONLY2.equals(currentPreferencesName));
+            }
+            assertEquals("Preferences names size not  3 ?", 3, preferencesNameCount);
+
+            final Map<String, String[]> preferencesMap = mockPortletPreferences.getMap();
+            assertNotNull("Preferences map null ?", preferencesMap);
+            assertEquals("Preferences map size not  3 ?", 3, preferencesMap.size());
+            assertTrue("Map does not contain expected key ?", preferencesMap.containsKey(PORTLET_WRITEABLE_ARRAY));
+            assertTrue("Map does not contain expected key ?", preferencesMap.containsKey(PORTLET_READONLY));
+            assertTrue("Map does not contain expected key ?", preferencesMap.containsKey(PORTLET_READONLY2));
+            assertTrue("Map does not contain expected value ?", preferencesMap.containsValue(PORTLET_WRITEABLE_ARRAY_VALUE));
+            assertTrue("Map does not contain expected value ?", preferencesMap.containsValue(mockPortletPreferences.getValues(PORTLET_READONLY, null)));
+            assertTrue("Map does not contain expected value ?", preferencesMap.containsValue(mockPortletPreferences.getValues(PORTLET_READONLY2, null)));
+        } catch (Exception ex) {
+            fail("Set/Get/Names/Map failed unexpectedly: " + ex);
+        }
+
+        // Verify store behaviour without validator present
+        try {
+            mockPortletPreferences.store();
+        } catch (Exception ex) {
+            fail("Store without validator failed ?  Exception: " + ex);
+        }
+        // Verify store behaviour with validator present
+        try {
+            mockPortletPreferences.setPreferencesValidator(new BasicPreferencesValidator());
+            mockPortletPreferences.store();
+            fail("Basic validator did not prevent store as expected ?");
+        } catch (Exception ex) {
+            assertTrue("Basic validator did not produce UnsupportedOperationException ?", ex instanceof UnsupportedOperationException);
+        }
+        // Verify store behaviour after validator cleared
+        try {
+            mockPortletPreferences.setPreferencesValidator(null);
+            mockPortletPreferences.store();
+        } catch (Exception ex) {
+            fail("Store after clearing validator failed ?  Exception: " + ex);
+        }
+    }
+
+    public void testMockPortletRequest() {
+        final String TEST_DEFAULT_CONTENT_TYPE = "text/html";
+        final String TEST_CONTENT_TYPE = "text/plain";
+        final String TEST_CONTENT_TYPE2 = "text/csv";
+        final String TEST_PROPERTY = "TEST_PROPERTY";
+        final String TEST_PROPERTY_VALUE = "Property_Value_1";
+        final String TEST_PROPERTY2 = "TEST_PROPERTY2";
+        final String TEST_PROPERTY2_VALUE = "Property_Value_2";
+        final String TEST_AUTHTYPE = "Test_Auth_Type";
+        final String TEST_FAKECONTEXTPATH = "/fakeContextPath";
+        final String TEST_FAKEREMOTEUSER = "fakeRemoteUser";
+        final String TEST_FAKEUSERROLE = "fakeUserRole";
+        final String TEST_ATTRIBUTE = "TEST_ATTRIBUTE";
+        final String TEST_ATTRIBUTE_VALUE = "Attribute_Value_1";
+        final String TEST_ATTRIBUTE2 = "TEST_ATTRIBUTE2";
+        final String TEST_ATTRIBUTE_VALUE2 = "Attribute_Value_2";
+        final String TEST_PARAM = "TEST_PARAM";
+        final String TEST_PARAM_VALUE = "Param_Value_1";
+        final String TEST_PARAM2 = "TEST_PARAM2";
+        final String TEST_PARAM_VALUE2 = "PARAM_Value_2";
+        final String TEST_ARRAYPARAM = "TEST_ARRAYPARAM";
+        final String[] TEST_PARAM_ARRAYVALUE = { "ARRAYVALUE1", "ARRAYVALUE2" };
+        final String TEST_SCHEME = "file";
+        final String TEST_HOST = "localhost2";
+        final int TEST_PORT = 8080;
+        final String TEST_WINDOWID = "TEST_WINDOWID";
+        final String TEST_COOKIE_ID = "TEST_COOKIE_ID";
+        final String TEST_COOKIE_ID2 = "TEST_COOKIE_ID2";
+        final String TEST_COOKIE_VALUE = "Cookie_Value";
+        final String TEST_COOKIE_VALUE2 = "Cookie_Value2";
+        final Cookie PORLET_TEST_COOKIE = new Cookie(TEST_COOKIE_ID, TEST_COOKIE_VALUE);
+        final Cookie PORLET_TEST_COOKIE2 = new Cookie(TEST_COOKIE_ID2, TEST_COOKIE_VALUE2);
+
+        // Call each constructor in sequence, do some basic checks
+        MockPortletRequest mockPortletRequest = new MockPortletRequest();
+        assertTrue("Default active state not true ?", mockPortletRequest.isActive());
+        assertEquals("Default portlet mode not as expected ?", PortletMode.VIEW, mockPortletRequest.getPortletMode());
+        assertEquals("Default window state not as expected ?", WindowState.NORMAL, mockPortletRequest.getWindowState());
+        assertNotNull("Default preferences is null ?", mockPortletRequest.getPreferences());
+        assertNotNull("Default portlet session is null ?", mockPortletRequest.getPortletSession());
+        assertNotNull("Default portal context is null ?", mockPortletRequest.getPortalContext());
+        assertEquals("Default context path not empty string ?", "", mockPortletRequest.getContextPath());
+        assertNull("Default remote user not null ?", mockPortletRequest.getRemoteUser());
+        assertNull("Default user principal not null ?", mockPortletRequest.getUserPrincipal());
+        assertFalse("Default secure state not false ?", mockPortletRequest.isSecure());
+        assertTrue("Default request sessionid not valid ?", mockPortletRequest.isRequestedSessionIdValid());
+        assertEquals("Default locale not as expected ?", Locale.ENGLISH, mockPortletRequest.getLocale());
+        assertEquals("Default response content-type not as expected ?", TEST_DEFAULT_CONTENT_TYPE, mockPortletRequest.getResponseContentType());
+        assertNull("Default lifecycle phase attribute not null ?", mockPortletRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE));
+        assertEquals("Default scheme not as expected ?", "http", mockPortletRequest.getScheme());
+        assertEquals("Default server name not as expected ?", "localhost", mockPortletRequest.getServerName());
+        assertEquals("Default server port not as expected ?", 80, mockPortletRequest.getServerPort());
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        mockPortletRequest = new MockPortletRequest(mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockPortletRequest = new MockPortletRequest(mockPortalContext, mockPortletContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockPortletRequest.getPortalContext());
+        mockPortletRequest.addResponseContentType(TEST_CONTENT_TYPE);
+        mockPortletRequest.addResponseContentType(TEST_CONTENT_TYPE2);
+        Enumeration<String> contentTypes = mockPortletRequest.getResponseContentTypes();
+        assertNotNull("MockPortletRequest content-types null ?", contentTypes);
+        final ArrayList<String> contentTypesList = new ArrayList<>();
+        while (contentTypes.hasMoreElements()) {
+            contentTypesList.add(contentTypes.nextElement());
+        }
+        assertEquals("MockPortletRequest does not contain three content-types ?", 3, contentTypesList.size());
+        assertTrue("MockPortletRequest does not contain content-type " + TEST_CONTENT_TYPE + " ?", contentTypesList.contains(TEST_CONTENT_TYPE));
+        assertTrue("MockPortletRequest does not contain content-type " + TEST_CONTENT_TYPE2 + " ?", contentTypesList.contains(TEST_CONTENT_TYPE2));
+        assertTrue("MockPortletRequest does not contain default content-type + " + TEST_DEFAULT_CONTENT_TYPE + " ?", contentTypesList.contains(TEST_DEFAULT_CONTENT_TYPE));
+        assertTrue("View portlet mode not allowed ?", mockPortletRequest.isPortletModeAllowed(PortletMode.VIEW));
+        assertTrue("Edit portlet mode not allowed ?", mockPortletRequest.isPortletModeAllowed(PortletMode.EDIT));
+        assertTrue("Help portlet mode not allowed ?", mockPortletRequest.isPortletModeAllowed(PortletMode.HELP));
+        assertTrue("Normal window state not allowed ?", mockPortletRequest.isWindowStateAllowed(WindowState.NORMAL));
+        assertTrue("Maximized window state not allowed ?", mockPortletRequest.isWindowStateAllowed(WindowState.MAXIMIZED));
+        assertTrue("Minimized window state not allowed ?", mockPortletRequest.isWindowStateAllowed(WindowState.MINIMIZED));
+        mockPortletRequest.setPortletMode(PortletMode.EDIT);
+        assertEquals("Portlet mode does not match set value ?", PortletMode.EDIT, mockPortletRequest.getPortletMode());
+        mockPortletRequest.setWindowState(WindowState.MAXIMIZED);
+        assertEquals("Window state does not match set value ?", WindowState.MAXIMIZED, mockPortletRequest.getWindowState());
+        final MockPortletPreferences mockPortletPreferences = new MockPortletPreferences();
+        mockPortletRequest.setPreferences(mockPortletPreferences);
+        assertEquals("Portlet preferences does not match set value ?", mockPortletPreferences, mockPortletRequest.getPreferences());
+        final MockPortletSession mockPortletSession = new MockPortletSession(mockPortletContext);
+        mockPortletRequest.setSession(mockPortletSession);
+        assertEquals("Portlet session does not match set value ?", mockPortletSession, mockPortletRequest.getPortletSession());
+        assertTrue("Set request sessionid not valid ?", mockPortletRequest.isRequestedSessionIdValid());
+        mockPortletRequest.setRequestedSessionIdValid(false);
+        assertFalse("Set request sessionid still valid after set invalid ?", mockPortletRequest.isRequestedSessionIdValid());
+        mockPortletRequest.setRequestedSessionIdValid(true);
+        assertTrue("Set request sessionid not valid after set valid ?", mockPortletRequest.isRequestedSessionIdValid());
+
+        mockPortletRequest.setProperty(TEST_PROPERTY, TEST_PROPERTY_VALUE);
+        mockPortletRequest.setProperty(TEST_PROPERTY2, TEST_PROPERTY2_VALUE);
+        final Enumeration<String> propertyNames = mockPortletRequest.getPropertyNames();
+        assertNotNull("MockPortalContext property names null ?", propertyNames);
+        final ArrayList<String> propertyNamesList = new ArrayList<>();
+        while (propertyNames.hasMoreElements()) {
+            propertyNamesList.add(propertyNames.nextElement());
+        }
+        assertEquals("MockPortletRequest does not contain two properties ?", 2, propertyNamesList.size());
+        assertTrue("MockPortletRequest does not contain property name " + TEST_PROPERTY + " ?", propertyNamesList.contains(TEST_PROPERTY));
+        assertTrue("MockPortletRequest does not contain property name " + TEST_PROPERTY2 + " ?", propertyNamesList.contains(TEST_PROPERTY2));
+        assertEquals("MockPortletRequest value of property name " + TEST_PROPERTY + " not as expected ?", TEST_PROPERTY_VALUE, mockPortletRequest.getProperty(TEST_PROPERTY));
+        assertEquals("MockPortletRequest value of property name " + TEST_PROPERTY2 + " not as expected ?", TEST_PROPERTY2_VALUE, mockPortletRequest.getProperty(TEST_PROPERTY2));
+        mockPortletRequest.addProperty(TEST_PROPERTY, TEST_PROPERTY2_VALUE);
+        final Enumeration<String> propertiesForName = mockPortletRequest.getProperties(TEST_PROPERTY);
+        assertNotNull("MockPortletRequest properties for " +  TEST_PROPERTY + " is null ?", propertiesForName);
+        final ArrayList<String> propertiesForNameList = new ArrayList<>();
+        while (propertiesForName.hasMoreElements()) {
+            propertiesForNameList.add(propertiesForName.nextElement());
+        }
+        assertEquals("MockPortletRequest does not contain two values for " +  TEST_PROPERTY + " ?", 2, propertiesForNameList.size());
+        assertTrue("MockPortletRequest does not contain expected value for property name " + TEST_PROPERTY + " ?", propertiesForNameList.contains(TEST_PROPERTY_VALUE));
+        assertTrue("MockPortletRequest does not contain expected value for propety name " + TEST_PROPERTY + " ?", propertiesForNameList.contains(TEST_PROPERTY2_VALUE));
+
+        mockPortletRequest.setAuthType(TEST_AUTHTYPE);
+        assertEquals("Authtype not equal to set value ?", TEST_AUTHTYPE, mockPortletRequest.getAuthType());
+        mockPortletRequest.setContextPath(TEST_FAKECONTEXTPATH);
+        assertEquals("Context path not equal to set value ?", TEST_FAKECONTEXTPATH, mockPortletRequest.getContextPath());
+        mockPortletRequest.setRemoteUser(TEST_FAKEREMOTEUSER);
+        assertEquals("Remote user not equal to set value ?", TEST_FAKEREMOTEUSER, mockPortletRequest.getRemoteUser());
+        final TestMockPrincipal testMockPrincipal = new TestMockPrincipal();
+        mockPortletRequest.setUserPrincipal(testMockPrincipal);
+        assertEquals("User principal not equal to set value ?", testMockPrincipal, mockPortletRequest.getUserPrincipal());
+        mockPortletRequest.addUserRole(TEST_FAKEUSERROLE);
+        assertTrue("Usser not in added role ?", mockPortletRequest.isUserInRole(TEST_FAKEUSERROLE));
+
+        assertNull(TEST_PARAM + " present before set ?", mockPortletRequest.getParameter(TEST_PARAM));
+        assertNull(TEST_PARAM2 + " present before set ?", mockPortletRequest.getParameter(TEST_PARAM));
+        mockPortletRequest.addParameter(TEST_PARAM, TEST_PARAM_VALUE);
+        mockPortletRequest.addParameter(TEST_PARAM2, TEST_PARAM_VALUE2);
+        assertEquals("Retrieved parameter 1 does not match set value ?", TEST_PARAM_VALUE, mockPortletRequest.getParameter(TEST_PARAM));
+        assertEquals("Retrieved parameter 2 does not match set value ?", TEST_PARAM_VALUE2, mockPortletRequest.getParameter(TEST_PARAM2));
+        final Enumeration<String> parameterNames = mockPortletRequest.getParameterNames();
+        int parameterNameCount = 0;
+        assertNotNull("Parameter names null after additions ?", parameterNames);
+        assertTrue("Parameter names empty after additions ?", parameterNames.hasMoreElements());
+        while (parameterNames.hasMoreElements()) {
+            parameterNameCount++;
+            String currentParameterName = parameterNames.nextElement();
+            assertTrue("Param name not one of two expected matches ?", TEST_PARAM.equals(currentParameterName) || 
+                    TEST_PARAM2.equals(currentParameterName));
+        }
+        assertEquals("Parameter names size not 2 ?", 2, parameterNameCount);
+        mockPortletRequest.addParameter(TEST_ARRAYPARAM, TEST_PARAM_ARRAYVALUE);
+        assertTrue("Array parameter value not set as expected ?", Arrays.equals(TEST_PARAM_ARRAYVALUE, mockPortletRequest.getParameterValues(TEST_ARRAYPARAM)));
+        final Map<String, String[]> parameterMap = mockPortletRequest.getParameterMap();
+        assertNotNull("Parameter map is null ?", parameterMap);
+        assertTrue("Expected parameter not present ?", parameterMap.containsKey(TEST_PARAM));
+        assertTrue("Expected parameter not present ?", parameterMap.containsKey(TEST_PARAM2));
+        assertTrue("Expected parameter not present ?", parameterMap.containsKey(TEST_ARRAYPARAM));
+
+        assertNull(TEST_ATTRIBUTE + " present before set ?", mockPortletRequest.getAttribute(TEST_ATTRIBUTE));
+        assertNull(TEST_ATTRIBUTE2 + " present before set ?", mockPortletRequest.getAttribute(TEST_ATTRIBUTE2));
+        mockPortletRequest.setAttribute(TEST_ATTRIBUTE, TEST_ATTRIBUTE_VALUE);
+        mockPortletRequest.setAttribute(TEST_ATTRIBUTE2, TEST_ATTRIBUTE_VALUE2);
+        assertEquals("Retrieved atribute 1 does not match set value ?", TEST_ATTRIBUTE_VALUE, mockPortletRequest.getAttribute(TEST_ATTRIBUTE));
+        assertEquals("Retrieved atribute 2 does not match set value ?", TEST_ATTRIBUTE_VALUE2, mockPortletRequest.getAttribute(TEST_ATTRIBUTE2));
+        final Enumeration<String> attributeNames = mockPortletRequest.getAttributeNames();
+        int attributeNameCount = 0;
+        assertNotNull("Attribute names null after additions ?", attributeNames);
+        assertTrue("Attribute names empty after additions ?", attributeNames.hasMoreElements());
+        while (attributeNames.hasMoreElements()) {
+            attributeNameCount++;
+            String currentAttibuteName = attributeNames.nextElement();
+            assertTrue("Attribute name not one of two or three expected matches ?", TEST_ATTRIBUTE.equals(currentAttibuteName) || 
+                    TEST_ATTRIBUTE2.equals(currentAttibuteName) ||
+                    PortletRequest.LIFECYCLE_PHASE.equals(currentAttibuteName));
+        }
+        assertTrue("Atribute names size not 2 or 3 ?", attributeNameCount >= 2 && attributeNameCount <= 3);
+
+        mockPortletRequest.addPreferredLocale(Locale.FRENCH);
+        assertEquals("Preferred locale not as expected ?", Locale.FRENCH, mockPortletRequest.getLocale());
+        final Enumeration<Locale> locales = mockPortletRequest.getLocales();
+        assertNotNull("Locales null after additions ?", locales);
+        assertTrue("Locales names empty after additions ?", locales.hasMoreElements());
+        final Locale firstLocale = locales.nextElement();
+        assertEquals("First locale not set value ?", Locale.FRENCH, firstLocale);
+        final Locale secondLocale = locales.nextElement();
+        assertEquals("Second locale not default value ?", Locale.ENGLISH, secondLocale);
+        mockPortletRequest.setScheme(TEST_SCHEME);
+        assertEquals("Scheme not equal to set value ?", TEST_SCHEME, mockPortletRequest.getScheme());
+        mockPortletRequest.setServerName(TEST_HOST);
+        assertEquals("Server name not equal to set value ?", TEST_HOST, mockPortletRequest.getServerName());
+        mockPortletRequest.setServerPort(TEST_PORT);
+        assertEquals("Server port not equal to set value ?", TEST_PORT, mockPortletRequest.getServerPort());
+        mockPortletRequest.setWindowID(TEST_WINDOWID);
+        assertEquals("Window id not equal to set value ?", TEST_WINDOWID, mockPortletRequest.getWindowID());
+
+        mockPortletRequest.setCookies(PORLET_TEST_COOKIE, PORLET_TEST_COOKIE2);
+        final Cookie[] cookies = mockPortletRequest.getCookies();
+        assertNotNull("Cookies array null ?", cookies);
+        assertEquals("Cookies array length not 2 ?", 2, cookies.length);
+        for (Cookie cookie : cookies) {
+            assertTrue("Cookie not one of set cookies ?", PORLET_TEST_COOKIE.equals(cookie) || PORLET_TEST_COOKIE2.equals(cookie));
+        }
+
+        final Map<String, String[]> originalPrivateParameters = mockPortletRequest.getPrivateParameterMap();
+        final Map<String, String[]> originalPublicParameters = mockPortletRequest.getPublicParameterMap();
+        assertNotNull("Original private parameters map is null ?", originalPrivateParameters);
+        assertFalse("Original private parameters map is empty ?", originalPrivateParameters.isEmpty());
+        assertTrue("Original private parameters map does not contain " + TEST_PARAM + " ?", originalPrivateParameters.containsKey(TEST_PARAM));
+        assertTrue("Original private parameters map does not contain " + TEST_PARAM2 + " ?", originalPrivateParameters.containsKey(TEST_PARAM2));
+        assertNotNull("Original public parameters map is null ?", originalPublicParameters);
+        assertTrue("Original public parameters map is not empty ?", originalPublicParameters.isEmpty());
+        mockPortletRequest.registerPublicParameter(TEST_PARAM2);
+        final Map<String, String[]> updatedPrivateParameters = mockPortletRequest.getPrivateParameterMap();
+        final Map<String, String[]> updatedPublicParameters = mockPortletRequest.getPublicParameterMap();
+        assertNotNull("Updated private parameters map is null ?", updatedPrivateParameters);
+        assertFalse("Updated private parameters map is empty ?", updatedPrivateParameters.isEmpty());
+        assertFalse("Updated private parameters map contains " + TEST_PARAM2 + " ?", updatedPrivateParameters.containsKey(TEST_PARAM2));
+        assertTrue("Updated private parameters map does not contain " + TEST_PARAM + " ?", updatedPrivateParameters.containsKey(TEST_PARAM));
+        assertNotNull("Updated public parameters map is null ?", updatedPublicParameters);
+        assertFalse("Updated public parameters map is empty ?", updatedPublicParameters.isEmpty());
+        assertFalse("Updated public parameters map contains " + TEST_PARAM + " ?", updatedPublicParameters.containsKey(TEST_PARAM));
+        assertTrue("Updated public parameters map does not contain " + TEST_PARAM2 + " ?", updatedPublicParameters.containsKey(TEST_PARAM2));
+    }
+
+    public void testMockPortletRequestDispatcher() {
+        final String TEST_DISPATCH_URL = "localhost:8080/fakeurl";
+
+        // Call each constructor in sequence, do some basic checks
+        MockPortletRequestDispatcher mockPortletRequestDispatcher = new MockPortletRequestDispatcher(TEST_DISPATCH_URL);
+        final MockRenderRequest mockRenderRequest = new MockRenderRequest();
+        final MockRenderResponse mockRenderResponse = new MockRenderResponse();
+        try {
+            mockPortletRequestDispatcher.include(mockRenderRequest, mockRenderResponse);
+        } catch (Exception ex) {
+            fail("MockPortletRequestDispatcher include (render) failed.  Exception: " + ex);
+        }
+        final MockPortletRequest mockPortletRequest = new MockPortletRequest();
+        final MockPortletResponse mockPortletResponse = new MockPortletResponse();
+        final MockMimeResponse mockMimeResponse = new MockMimeResponse();
+        try {
+            mockPortletRequestDispatcher.include(mockPortletRequest, mockPortletResponse);
+            fail("Include of non-MockMimeResponse did not throw an exception ?");
+        } catch (IllegalArgumentException iae) {
+             // Expected failure
+        } catch (Exception ex) {
+            fail("MockPortletRequestDispatcher include (portlet) failed.  Exception: " + ex);
+        }
+        try {
+            mockPortletRequestDispatcher.include(mockPortletRequest, mockMimeResponse);
+        } catch (Exception ex) {
+            fail("MockPortletRequestDispatcher include (portlet) failed.  Exception: " + ex);
+        }
+        try {
+            mockPortletRequestDispatcher.forward(mockPortletRequest, mockPortletResponse);
+            fail("Forward of non-MockMimeResponse did not throw an exception ?");
+        } catch (IllegalArgumentException iae) {
+             // Expected failure
+        } catch (Exception ex) {
+            fail("MockPortletRequestDispatcher forward failed.  Exception: " + ex);
+        }
+        try {
+            mockPortletRequestDispatcher.forward(mockPortletRequest, mockMimeResponse);
+        } catch (Exception ex) {
+            fail("MockPortletRequestDispatcher forward failed.  Exception: " + ex);
+        }
+    }
+
+    public void testMockPortletResponse() {
+        final String TEST_ENCODE_URL = "localhost:8080/fakeforwardedurl?param1=param with spaces";
+        final String TEST_NAMESPACE = "Test_Namespace";
+        final String TEST_PROPERTY = "TEST_PROPERTY";
+        final String TEST_PROPERTY_VALUE = "Property_Value_1";
+        final String TEST_PROPERTY2 = "TEST_PROPERTY2";
+        final String TEST_PROPERTY2_VALUE = "Property_Value_2";
+        final String TEST_XMLPROPERTY = "TEST_XMLPROPERTY";
+        final String TEST_XML_TAGNAME = "TEST_XML_TAGNAME";
+        final String TEST_COOKIE_ID = "TEST_COOKIE_ID";
+
+        // Call each constructor in sequence, do some basic checks
+        MockPortletResponse mockPortletResponse = new MockPortletResponse();
+        assertEquals("Default namespace not as expected ?", "", mockPortletResponse.getNamespace());
+        assertNotNull("Default portal context null ?", mockPortletResponse.getPortalContext());
+        assertEquals("Encoded URL not as expected ?", TEST_ENCODE_URL, mockPortletResponse.encodeURL(TEST_ENCODE_URL));  // Mock URL encode does nothing
+        mockPortletResponse.setNamespace(TEST_NAMESPACE);
+        assertEquals("Set namespace not as expected ?", TEST_NAMESPACE, mockPortletResponse.getNamespace());
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockPortletResponse = new MockPortletResponse(mockPortalContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockPortletResponse.getPortalContext());
+
+        mockPortletResponse.setProperty(TEST_PROPERTY, TEST_PROPERTY_VALUE);
+        mockPortletResponse.setProperty(TEST_PROPERTY2, TEST_PROPERTY2_VALUE);
+        final Set<String> propertyNames = mockPortletResponse.getPropertyNames();
+        assertNotNull("MockPortletResponse property names null ?", propertyNames);
+        assertEquals("MockPortletResponse does not contain two properties ?", 2, propertyNames.size());
+        assertTrue("MockPortletResponse does not contain property name " + TEST_PROPERTY + " ?", propertyNames.contains(TEST_PROPERTY));
+        assertTrue("MockPortletResponse does not contain property name " + TEST_PROPERTY2 + " ?", propertyNames.contains(TEST_PROPERTY2));
+        assertEquals("MockPortletResponse value of property name " + TEST_PROPERTY + " not as expected ?", TEST_PROPERTY_VALUE, mockPortletResponse.getProperty(TEST_PROPERTY));
+        assertEquals("MockPortletResponse value of property name " + TEST_PROPERTY2 + " not as expected ?", TEST_PROPERTY2_VALUE, mockPortletResponse.getProperty(TEST_PROPERTY2));
+        mockPortletResponse.addProperty(TEST_PROPERTY, TEST_PROPERTY2_VALUE);
+        final String[] propertiesForName = mockPortletResponse.getProperties(TEST_PROPERTY);
+        assertNotNull("MockPortletResponse properties for " +  TEST_PROPERTY + " is null ?", propertiesForName);
+        final ArrayList<String> propertiesForNameList = new ArrayList<>();
+        propertiesForNameList.addAll(Arrays.asList(propertiesForName));
+        assertEquals("MockPortletResponse does not contain two values for " +  TEST_PROPERTY + " ?", 2, propertiesForNameList.size());
+        assertTrue("MockPortletResponse does not contain expected value for property name " + TEST_PROPERTY + " ?", propertiesForNameList.contains(TEST_PROPERTY_VALUE));
+        assertTrue("MockPortletResponse does not contain expected value for propety name " + TEST_PROPERTY + " ?", propertiesForNameList.contains(TEST_PROPERTY2_VALUE));
+
+        final Cookie[] cookies = mockPortletResponse.getCookies();
+        assertNotNull("Cookies array null ?", cookies);
+        assertEquals("Cookies array length not 0 ?", 0, cookies.length);
+        assertNull("Cookie found ?", mockPortletResponse.getCookie(TEST_COOKIE_ID));
+
+        Set<String> xmlPropertyNames = mockPortletResponse.getXmlPropertyNames();
+        assertNotNull("MockPortletResponse XML property names null ?", xmlPropertyNames);
+        assertTrue("XML properites set not empty ?", xmlPropertyNames.isEmpty());
+        assertNull("XML property found ?", mockPortletResponse.getXmlProperty(TEST_XMLPROPERTY));
+        Element[] xmlProperties = mockPortletResponse.getXmlProperties(TEST_XMLPROPERTY);
+        assertNull("XML properties found ?", xmlProperties);
+        Element element = mockPortletResponse.createElement(TEST_XML_TAGNAME);
+        assertNotNull("MockPortletResponse XML element null ?", element);
+        mockPortletResponse.addProperty(TEST_XMLPROPERTY, element);
+        xmlPropertyNames = mockPortletResponse.getXmlPropertyNames();
+        assertNotNull("MockPortletResponse XML property names null ?", xmlPropertyNames);
+        assertFalse("XML properites set empty ?", xmlPropertyNames.isEmpty());
+        assertNotNull("XML property not found ?", mockPortletResponse.getXmlProperty(TEST_XMLPROPERTY));
+        xmlProperties = mockPortletResponse.getXmlProperties(TEST_XMLPROPERTY);
+        assertNotNull("XML properties not found ?", xmlProperties);
+        assertEquals("XML properties not expected size ?", 1, xmlProperties.length);
+        assertEquals("XML property at index 0 not element value ?", element, xmlProperties[0]);
+    }
+
+    public void testMockPortletSession() {
+        final String TEST_ATTRIBUTE = "TEST_ATTRIBUTE";
+        final String TEST_ATTRIBUTE_VALUE = "Attribute_Value_1";
+        final String TEST_ATTRIBUTE2 = "TEST_ATTRIBUTE2";
+        final String TEST_ATTRIBUTE_VALUE2 = "Attribute_Value_2";
+        final long TEST_START_TIME = System.currentTimeMillis();
+        final int TEST_MAXINACTIVE_INTERVAL = 300;
+
+        // Call each constructor in sequence, do some basic checks
+        MockPortletSession mockPortletSession = new MockPortletSession();
+        final String INITIAL_SESSION_ID = mockPortletSession.getId();
+        assertNotNull("MockPortletSession session id null ?", INITIAL_SESSION_ID);
+        assertFalse("MockPortletSession session is invalid ?", mockPortletSession.isInvalid());
+        assertTrue("MockPortletSession session is not new ?", mockPortletSession.isNew());
+        mockPortletSession.setNew(false);
+        assertFalse("MockPortletSession session is still new ?", mockPortletSession.isNew());
+        mockPortletSession.setNew(true);
+        assertTrue("MockPortletSession session is not new after set ?", mockPortletSession.isNew());
+        assertEquals("MockPortletSession max inactive interval not 0 ?", 0, mockPortletSession.getMaxInactiveInterval());
+        assertNotNull("Default portlet context null ?", mockPortletSession.getPortletContext());
+        assertTrue("MockPortletSession session creation time in past ?", mockPortletSession.getCreationTime() >= TEST_START_TIME);
+        assertTrue("MockPortletSession session last accessed time in past ?", mockPortletSession.getLastAccessedTime() >= TEST_START_TIME);
+        assertTrue("MockPortletSession session last accessed time older than creation time ?", mockPortletSession.getLastAccessedTime() >= mockPortletSession.getCreationTime());
+        assertNotNull("Default portlet attribute map null ?", mockPortletSession.getAttributeMap());
+        assertNotNull("Default portlet portletscope attribute map null ?", mockPortletSession.getAttributeMap(PortletSession.PORTLET_SCOPE));
+        assertNotNull("Default portlet applicationscope attribute map null ?", mockPortletSession.getAttributeMap(PortletSession.APPLICATION_SCOPE));
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        mockPortletSession = new MockPortletSession(mockPortletContext);
+        assertEquals("MockPortletSession portletcontext not equal to set value ?", mockPortletContext, mockPortletSession.getPortletContext());
+        final String NEW_SESSION_ID = mockPortletSession.getId();
+        assertNotNull("MockPortletSession session id null ?", NEW_SESSION_ID);
+        assertFalse("Original and new session id match ?", NEW_SESSION_ID.equals(INITIAL_SESSION_ID));
+
+        assertNull(TEST_ATTRIBUTE + " present before set ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE));
+        assertNull(TEST_ATTRIBUTE2 + " present before set ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE2));
+        mockPortletSession.setAttribute(TEST_ATTRIBUTE, TEST_ATTRIBUTE_VALUE);
+        mockPortletSession.setAttribute(TEST_ATTRIBUTE2, TEST_ATTRIBUTE_VALUE2);
+        assertEquals("Retrieved atribute 1 does not match set value ?", TEST_ATTRIBUTE_VALUE, mockPortletSession.getAttribute(TEST_ATTRIBUTE));
+        assertEquals("Retrieved atribute 2 does not match set value ?", TEST_ATTRIBUTE_VALUE2, mockPortletSession.getAttribute(TEST_ATTRIBUTE2));
+        final Enumeration<String> attributeNames = mockPortletSession.getAttributeNames();
+        int attributeNameCount = 0;
+        assertNotNull("Attribute names null after additions ?", attributeNames);
+        assertTrue("Attribute names empty after additions ?", attributeNames.hasMoreElements());
+        while (attributeNames.hasMoreElements()) {
+            attributeNameCount++;
+            String currentAttibuteName = attributeNames.nextElement();
+            assertTrue("Attribute name not one of two expected matches ?", TEST_ATTRIBUTE.equals(currentAttibuteName) || 
+                    TEST_ATTRIBUTE2.equals(currentAttibuteName));
+        }
+        assertEquals("Atribute names size not expected value ?", 2, attributeNameCount);
+        assertNull(TEST_ATTRIBUTE + " present before set (application scope) ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE, PortletSession.APPLICATION_SCOPE));
+        assertNull(TEST_ATTRIBUTE2 + " present before set (application scope) ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE2, PortletSession.APPLICATION_SCOPE));
+        mockPortletSession.setAttribute(TEST_ATTRIBUTE, TEST_ATTRIBUTE_VALUE, PortletSession.APPLICATION_SCOPE);
+        mockPortletSession.setAttribute(TEST_ATTRIBUTE2, TEST_ATTRIBUTE_VALUE2, PortletSession.APPLICATION_SCOPE);
+        assertEquals("Retrieved atribute 1 does not match set value (application scope) ?", TEST_ATTRIBUTE_VALUE, mockPortletSession.getAttribute(TEST_ATTRIBUTE, PortletSession.APPLICATION_SCOPE));
+        assertEquals("Retrieved atribute 2 does not match set value (application scope) ?", TEST_ATTRIBUTE_VALUE2, mockPortletSession.getAttribute(TEST_ATTRIBUTE2, PortletSession.APPLICATION_SCOPE));
+        final Enumeration<String> attributeNamesApplication = mockPortletSession.getAttributeNames(PortletSession.APPLICATION_SCOPE);
+        int attributeNameCountApplication = 0;
+        assertNotNull("Attribute names null after additions (application scope) ?", attributeNamesApplication);
+        assertTrue("Attribute names empty after additions (application scope) ?", attributeNamesApplication.hasMoreElements());
+        while (attributeNamesApplication.hasMoreElements()) {
+            attributeNameCountApplication++;
+            String currentAttibuteName = attributeNamesApplication.nextElement();
+            assertTrue("Attribute name not one of two expected matches (application scope) ?", TEST_ATTRIBUTE.equals(currentAttibuteName) || 
+                    TEST_ATTRIBUTE2.equals(currentAttibuteName));
+        }
+        assertEquals("Atribute names size not expected value (application scope) ?", 2, attributeNameCount);
+
+        assertTrue("MockPortletSession session is not new ?", mockPortletSession.isNew());
+        mockPortletSession.access();
+        assertFalse("MockPortletSession session is still new after access ?", mockPortletSession.isNew());
+        mockPortletSession.setMaxInactiveInterval(TEST_MAXINACTIVE_INTERVAL);
+        assertEquals("MockPortletSession max inactive interval not set value ?", TEST_MAXINACTIVE_INTERVAL, mockPortletSession.getMaxInactiveInterval());
+        mockPortletSession.removeAttribute(TEST_ATTRIBUTE2);
+        assertNull(TEST_ATTRIBUTE2 + " still present after removal ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE2));
+        mockPortletSession.removeAttribute(TEST_ATTRIBUTE, PortletSession.APPLICATION_SCOPE);
+        assertNull(TEST_ATTRIBUTE + " still present after removal (application scope) ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE, PortletSession.APPLICATION_SCOPE));
+        final long BEFORE_INVALIDATE_TIME = System.currentTimeMillis();
+        mockPortletSession.invalidate();
+        assertTrue("MockPortletSession session is still valid after invalidate ?", mockPortletSession.isInvalid());
+        assertNull(TEST_ATTRIBUTE + " still present after invalidate ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE));
+        assertNull(TEST_ATTRIBUTE2 + " still present after removal (application scope) ?", mockPortletSession.getAttribute(TEST_ATTRIBUTE2, PortletSession.APPLICATION_SCOPE));
+        assertTrue("MockPortletSession session last accessed time older than creation time ?", mockPortletSession.getLastAccessedTime() >= mockPortletSession.getCreationTime());
+        assertTrue("MockPortletSession session last accessed time older than time before invalidation ?", mockPortletSession.getLastAccessedTime() >= BEFORE_INVALIDATE_TIME);
+    }
+
+    public void testMockPortletURL() {
+        final String TEST_PARAMETER = "TEST_PARAMETER";
+
+        // Call each constructor in sequence, do some basic checks
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        final MockPortletURL mockPortletURL = new MockPortletURL(mockPortalContext, MockPortletURL.URL_TYPE_RENDER);
+        assertNull("MockPortletURL default portlet mode not null ?", mockPortletURL.getPortletMode());
+        assertNull("MockPortletURL default window state not null ?", mockPortletURL.getWindowState());
+        try {
+            mockPortletURL.setWindowState(WindowState.MAXIMIZED);
+        } catch (Exception ex) {
+            fail("MockPortletURL window state set failed.  Exception: " + ex);
+        }
+        assertEquals("Window state does not match set value ?", WindowState.MAXIMIZED, mockPortletURL.getWindowState());
+        try {
+            mockPortletURL.setPortletMode(PortletMode.VIEW);
+        } catch (Exception ex) {
+            fail("MockPortletURL portlet mode set failed.  Exception: " + ex);
+        }
+        assertEquals("PortletMode.VIEW not set ?", PortletMode.VIEW, mockPortletURL.getPortletMode());
+        final String portletURLAsString = mockPortletURL.toString();
+        assertNotNull("MockPortletURL toString() result null ?", portletURLAsString);
+        assertTrue("MockPortletURL toString() result is empty ?", portletURLAsString.length() > 0);
+        mockPortletURL.removePublicRenderParameter(TEST_PARAMETER);  // Just confirm no exception
+    }
+
+    public void testMockRenderRequest() {
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+
+        // Call each constructor in sequence, do some basic checks
+        MockRenderRequest mockRenderRequest = new MockRenderRequest();
+        assertNull("MockRenderRequest ETag not null ?", mockRenderRequest.getETag());
+        mockRenderRequest = new MockRenderRequest(PortletMode.HELP);
+        assertEquals("MockRenderRequest portlet mode does not match constructor-set value ?", PortletMode.HELP, mockRenderRequest.getPortletMode());
+        mockRenderRequest = new MockRenderRequest(PortletMode.HELP, WindowState.MAXIMIZED);
+        assertEquals("MockRenderRequest portlet mode does not match constructor-set value ?", PortletMode.HELP, mockRenderRequest.getPortletMode());
+        assertEquals("MockRenderRequest window state does not match constructor-set value ?", WindowState.MAXIMIZED, mockRenderRequest.getWindowState());
+        mockRenderRequest = new MockRenderRequest(mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockRenderRequest = new MockRenderRequest(mockPortalContext, mockPortletContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockRenderRequest.getPortalContext());
+    }
+
+    public void testMockRenderResponse() {
+        final String TEST_TITLE = "TEST_TITLE";
+
+        // Call each constructor in sequence, do some basic checks
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        final MockRenderRequest mockRenderRequest = new MockRenderRequest();
+        MockRenderResponse mockRenderResponse = new MockRenderResponse();
+        assertNull("MockRenderResponse title not null ?", mockRenderResponse.getTitle());
+        mockRenderResponse = new MockRenderResponse(mockPortalContext);    // Only test is to confirm constructor completes without exception
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockRenderResponse.getPortalContext());
+        mockRenderResponse = new MockRenderResponse(mockPortalContext, mockRenderRequest);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockRenderResponse.getPortalContext());
+        mockRenderResponse.setTitle(TEST_TITLE);
+        assertEquals("Title does not match set value ?", TEST_TITLE, mockRenderResponse.getTitle());
+        final Collection<PortletMode> portletModes = new ArrayList<>();
+        portletModes.add(PortletMode.EDIT);
+        portletModes.add(PortletMode.VIEW);
+        mockRenderResponse.setNextPossiblePortletModes(portletModes);
+        assertEquals("Next possible portlet modes not equal to set value ?", portletModes, mockRenderResponse.getNextPossiblePortletModes());
+    }
+
+    public void testMockResourceRequest() {
+        final String TEST_RESOURCEID = "TEST_RESOURCEID";
+        final String TEST_CACHEABILITY = "TEST_CACHEABILITY";
+        final String TEST_PARAM = "TEST_PARAM";
+        final String TEST_PARAM2 = "TEST_PARAM2";
+        final String TEST_PARAM_VALUE = "NORMALVALUE";
+        final String[] TEST_PARAM_ARRAYVALUE = { "ARRAYVALUE1", "ARRAYVALUE2" };
+
+        // Call each constructor in sequence, do some basic checks
+        MockResourceRequest mockResourceRequest = new MockResourceRequest();
+        assertNull("Default resource id not null ?", mockResourceRequest.getResourceID());
+        assertNull("Default cacheability not null ?", mockResourceRequest.getCacheability());
+        assertNull("Default ETag not null ?", mockResourceRequest.getETag());
+        mockResourceRequest = new MockResourceRequest(TEST_RESOURCEID);
+        assertEquals("Resource id not set value ?", TEST_RESOURCEID, mockResourceRequest.getResourceID());
+        final MockPortletContext mockPortletContext = new MockPortletContext();
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        final MockResourceURL mockResourceURL = new MockResourceURL();
+        mockResourceRequest = new MockResourceRequest(mockResourceURL);     // Only test is to confirm constructor completes without exception
+        mockResourceRequest = new MockResourceRequest(mockPortletContext);  // Only test is to confirm constructor completes without exception
+        mockResourceRequest = new MockResourceRequest(mockPortalContext, mockPortletContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockResourceRequest.getPortalContext());
+        mockResourceRequest.setCacheability(TEST_CACHEABILITY);
+        assertEquals("Cacheability does not match set value ?", TEST_CACHEABILITY, mockResourceRequest.getCacheability());
+        mockResourceRequest.addPrivateRenderParameter(TEST_PARAM, TEST_PARAM_VALUE);
+        mockResourceRequest.addPrivateRenderParameter(TEST_PARAM2, TEST_PARAM_ARRAYVALUE);
+        final Map<String, String[]> privateRenderParameterMap = mockResourceRequest.getPrivateRenderParameterMap();
+        assertNotNull("Parameter map is null ?", privateRenderParameterMap);
+        assertTrue("Expected parameter not present ?", privateRenderParameterMap.containsKey(TEST_PARAM));
+        assertTrue("Expected parameter not present ?", privateRenderParameterMap.containsKey(TEST_PARAM2));
+    }
+
+    public void testMockResourceResponse() {
+        final int TEST_CONTENT_LENGTH = 4096;
+
+        // Call each constructor in sequence, do some basic checks
+        MockResourceResponse mockResourceRequest = new MockResourceResponse();
+        assertEquals("MockResourceResponse initial content length not 0 ?", 0, mockResourceRequest.getContentLength());
+        mockResourceRequest.setContentLength(TEST_CONTENT_LENGTH);
+        assertEquals("Content length not equal to set value ?", TEST_CONTENT_LENGTH, mockResourceRequest.getContentLength());
+    }
+
+    public void testMockResourceURL() {
+        final String TEST_RESOURCEID = "TEST_RESOURCEID";
+        final String TEST_CACHEABILITY = "TEST_CACHEABILITY";
+
+        // Call each constructor in sequence, do some basic checks
+        MockResourceURL mockResourceURL = new MockResourceURL();
+        assertNull("Default resource id not null ?", mockResourceURL.getResourceID());
+        assertNull("Default cacheability not null ?", mockResourceURL.getCacheability());
+        mockResourceURL.setResourceID(TEST_RESOURCEID);
+        assertEquals("Resource id not set value ?", TEST_RESOURCEID, mockResourceURL.getResourceID());
+        mockResourceURL.setCacheability(TEST_CACHEABILITY);
+        assertEquals("Cacheability does not match set value ?", TEST_CACHEABILITY, mockResourceURL.getCacheability());
+        final String resourceURLAsString = mockResourceURL.toString();
+        assertNotNull("MockResourceURL toString() result null ?", resourceURLAsString);
+        assertTrue("MockResourceURL toString() result is empty ?", resourceURLAsString.length() > 0);
+    }
+
+    public void testMockStateAwareResponse() {
+        final String TEST_PORTLETNAME = "TestPortletName";
+        final String TEST_DEFAULTNAMESPACE = "Test_Default_Namespace";
+        final String TEST_QNAME3_STRING = "TestQname3String";
+        final QName TEST_QNAME = new QName(TEST_PORTLETNAME);
+        final QName TEST_QNAME2 = new QName(TEST_DEFAULTNAMESPACE);
+        final String TEST_QNAME_VALUE = "Test_Qname_Value";
+        final String TEST_QNAME2_VALUE = "Test_Qname2_Value";
+        final String TEST_QNAME3_VALUE = "Test_Qname3_Value";
+        final String TEST_PARAM = "TEST_PARAM";
+        final String TEST_PARAM2 = "TEST_PARAM2";
+        final String TEST_PARAM3 = "TEST_PARAM3";
+        final String TEST_PARAM_VALUE = "NORMALVALUE";
+        final String[] TEST_PARAM_ARRAYVALUE = { "ARRAYVALUE1", "ARRAYVALUE2" };
+        final String[] TEST_PARAM_ARRAYVALUE2 = { "ARRAYVALUE3", "ARRAYVALUE4" };
+
+        // Call each constructor in sequence, do some basic checks
+        MockStateAwareResponse mockStateAwareResponse = new MockStateAwareResponse();
+        try {
+            mockStateAwareResponse.setPortletMode(PortletMode.VIEW);
+        } catch (Exception ex) {
+            fail("MockStateAwareResponse portlet mode set failed.  Exception: " + ex);
+        }
+        assertEquals("PortletMode.VIEW not set ?", PortletMode.VIEW, mockStateAwareResponse.getPortletMode());
+        try {
+            mockStateAwareResponse.setPortletMode(PortletMode.EDIT);
+        } catch (Exception ex) {
+            fail("MockStateAwareResponse portlet mode set failed.  Exception: " + ex);
+        }
+        assertEquals("Portlet mode does not match set value ?", PortletMode.EDIT, mockStateAwareResponse.getPortletMode());
+        try {
+            mockStateAwareResponse.setWindowState(WindowState.MAXIMIZED);
+        } catch (Exception ex) {
+            fail("MockStateAwareResponse portlet mode set failed.  Exception: " + ex);
+        }
+        assertEquals("Window state does not match set value ?", WindowState.MAXIMIZED, mockStateAwareResponse.getWindowState());
+        final MockPortalContext mockPortalContext = new MockPortalContext();
+        mockStateAwareResponse = new MockStateAwareResponse(mockPortalContext);
+        assertEquals("Portal context does not match constructor-set value ?", mockPortalContext, mockStateAwareResponse.getPortalContext());
+
+        assertNull("Initial event already present ?", mockStateAwareResponse.getEvent(TEST_PORTLETNAME));
+        assertNull("Initial event already present ?", mockStateAwareResponse.getEvent(TEST_QNAME));
+        assertNull("Initial event already present ?", mockStateAwareResponse.getEvent(TEST_DEFAULTNAMESPACE));
+        assertNull("Initial event already present ?", mockStateAwareResponse.getEvent(TEST_QNAME2));
+        mockStateAwareResponse.setEvent(TEST_QNAME, TEST_QNAME_VALUE);
+        mockStateAwareResponse.setEvent(TEST_QNAME2, TEST_QNAME2_VALUE);
+        mockStateAwareResponse.setEvent(TEST_QNAME3_STRING, TEST_QNAME3_VALUE);
+        assertEquals("First event QName not set value ?", TEST_QNAME_VALUE, mockStateAwareResponse.getEvent(TEST_QNAME));
+        assertEquals("Second event QName not set value ?", TEST_QNAME2_VALUE, mockStateAwareResponse.getEvent(TEST_QNAME2));
+        assertEquals("Third event QName not set value ?", TEST_QNAME3_VALUE, mockStateAwareResponse.getEvent(TEST_QNAME3_STRING));
+        final Iterator<QName> eventQNames = mockStateAwareResponse.getEventNames();
+        assertNotNull("Event names iterator null ?", eventQNames);
+        while (eventQNames.hasNext()) {
+            QName currentQName = eventQNames.next();
+            assertNotNull("Iterator QName entry null ?", currentQName);
+            assertTrue("Iterator QName name not one of two or three expected matches ?", currentQName.getLocalPart().equals(TEST_QNAME.getLocalPart()) || 
+                    currentQName.getLocalPart().equals(TEST_QNAME2.getLocalPart()) ||
+                    currentQName.getLocalPart().equals(TEST_QNAME3_STRING));
+        }
+
+        mockStateAwareResponse.setRenderParameter(TEST_PARAM, TEST_PARAM_VALUE);
+        mockStateAwareResponse.setRenderParameter(TEST_PARAM2, TEST_PARAM_ARRAYVALUE);
+        assertEquals("Portal test render parameter value not as expected ?", TEST_PARAM_VALUE, mockStateAwareResponse.getRenderParameter(TEST_PARAM));
+        assertEquals("Portal test render parameter value not as expected ?", TEST_PARAM_ARRAYVALUE[0], mockStateAwareResponse.getRenderParameter(TEST_PARAM2));
+        final Iterator<String> renderParameterNames = mockStateAwareResponse.getRenderParameterNames();
+        assertNotNull("Render parameter names iterator null ?", renderParameterNames);
+        while (renderParameterNames.hasNext()) {
+            String currentParameter = renderParameterNames.next();
+            assertNotNull("Iterator parameter entry null ?", currentParameter);
+            assertTrue("Iterator parameter name not one of two or three expected matches ?", currentParameter.equals(TEST_PARAM) || 
+                    currentParameter.equals(TEST_PARAM2) ||
+                    currentParameter.equals(TEST_PARAM3));
+        }
+        final String[] renderParameterValuesParam2 = mockStateAwareResponse.getRenderParameterValues(TEST_PARAM2);
+        assertTrue("Render parameter value2 not as expected ?", Arrays.equals(TEST_PARAM_ARRAYVALUE, renderParameterValuesParam2));
+        final Map<String, String[]> renderParametersMap = new LinkedHashMap<>();
+        renderParametersMap.put(TEST_PARAM3, TEST_PARAM_ARRAYVALUE2);
+        mockStateAwareResponse.setRenderParameters(renderParametersMap);
+        assertEquals("Portal test render parameter value not cleared as expected ?", null, mockStateAwareResponse.getRenderParameter(TEST_PARAM));
+        assertEquals("Portal test render parameter value not cleared as expected ?", null, mockStateAwareResponse.getRenderParameter(TEST_PARAM2));
+        assertEquals("Portal test render parameter map value not as expected ?", renderParametersMap, mockStateAwareResponse.getRenderParameterMap());
+    }
+
+    public void testServletWrappingPortletContext() {
+        final String TEST_ATTRIBUTE = "TEST_ATTRIBUTE";
+        final String TEST_ATTRIBUTE_VALUE = "Attribute_Value_1";
+        final String TEST_ATTRIBUTE2 = "TEST_ATTRIBUTE2";
+        final String TEST_ATTRIBUTE_VALUE2 = "Attribute_Value_2";
+        final String TEST_INITPARAM = "TEST_INITPARAM";
+        final String TEST_INITPARAM2 = "TEST_INITPARAM2";
+
+        // Call each constructor in sequence, do some basic checks
+        final MockServletContext mockServletContext = new MockServletContext();
+        final ServletWrappingPortletContext servletWrappingPortletContext = new ServletWrappingPortletContext(mockServletContext);
+        assertNotNull("ServletWrappingPortletContext serverInfo null ?", servletWrappingPortletContext.getServerInfo());
+        assertNull("ServletWrappingPortletContext PortletRequestDispatcher not null (illegal prefix) ?", servletWrappingPortletContext.getRequestDispatcher("IllegalPrefix"));
+        assertNull("ServletWrappingPortletContext PortletRequestDispatcher not null ?", servletWrappingPortletContext.getRequestDispatcher("/"));
+        assertNotNull("ServletWrappingPortletContext resource stream for / null ?", servletWrappingPortletContext.getResourceAsStream("/"));
+        assertNull("ServletWrappingPortletContext resource stream for /ThisDoesNotExist not null ?", servletWrappingPortletContext.getResourceAsStream("/ThisDoesNotExist"));
+        assertTrue("ServletWrappingPortletContext major version not >= 2 ?", servletWrappingPortletContext.getMajorVersion() >= 2);
+        assertTrue("ServletWrappingPortletContext minor version not >= 0 ?", servletWrappingPortletContext.getMajorVersion() >= 0);
+        assertNull("ServletWrappingPortletContext MIME type for / not null ?", servletWrappingPortletContext.getMimeType("/"));
+        assertNotNull("ServletWrappingPortletContext real path for / null ?", servletWrappingPortletContext.getRealPath("/"));
+        assertNull("ServletWrappingPortletContext real path for /ThisDoesNotExist not null ? ?", servletWrappingPortletContext.getRealPath("/ThisDoesNotExist"));
+        assertNull("ServletWrappingPortletContext resource paths for / not null ?", servletWrappingPortletContext.getResourcePaths("/"));
+        assertNull("ServletWrappingPortletContext resource paths for / null ?", servletWrappingPortletContext.getResourcePaths("/ThisDoesNotExist"));
+        try {
+            assertNotNull("ServletWrappingPortletContext resource URL for / null ?", servletWrappingPortletContext.getResource("/"));
+        } catch (MalformedURLException mue) {
+            fail("ServletWrappingPortletContext resource URL for / failed.  Exception: " + mue);
+        }
+        try {
+            assertNull("ServletWrappingPortletContext resource URL for /ThisDoesNotExist not null ?", servletWrappingPortletContext.getResource("/ThisDoesNotExist"));
+        } catch (MalformedURLException mue) {
+            fail("ServletWrappingPortletContext resource URL for /ThisDoesNotExist failed.  Exception: " + mue);
+        }
+
+        assertNull(TEST_ATTRIBUTE + " present before set ?", servletWrappingPortletContext.getAttribute(TEST_ATTRIBUTE));
+        assertNull(TEST_ATTRIBUTE2 + " present before set ?", servletWrappingPortletContext.getAttribute(TEST_ATTRIBUTE2));
+        servletWrappingPortletContext.setAttribute(TEST_ATTRIBUTE, TEST_ATTRIBUTE_VALUE);
+        servletWrappingPortletContext.setAttribute(TEST_ATTRIBUTE2, TEST_ATTRIBUTE_VALUE2);
+        assertEquals("Retrieved atribute 1 does not match set value ?", TEST_ATTRIBUTE_VALUE, servletWrappingPortletContext.getAttribute(TEST_ATTRIBUTE));
+        assertEquals("Retrieved atribute 2 does not match set value ?", TEST_ATTRIBUTE_VALUE2, servletWrappingPortletContext.getAttribute(TEST_ATTRIBUTE2));
+        final Enumeration<String> attributeNames = servletWrappingPortletContext.getAttributeNames();
+        int attributeNameCount = 0;
+        assertNotNull("Attribute names null after additions ?", attributeNames);
+        assertTrue("Attribute names empty after additions ?", attributeNames.hasMoreElements());
+        while (attributeNames.hasMoreElements()) {
+            attributeNameCount++;
+            String currentAttibuteName = attributeNames.nextElement();
+            assertTrue("Attribute name not one of two or three expected matches ?", TEST_ATTRIBUTE.equals(currentAttibuteName) || 
+                    TEST_ATTRIBUTE2.equals(currentAttibuteName) ||
+                    WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE.equals(currentAttibuteName));
+        }
+        assertTrue("Atribute names size not 2 or 3 ?", attributeNameCount >= 2 && attributeNameCount <= 3);
+
+        assertNull(TEST_INITPARAM + " present before set ?", servletWrappingPortletContext.getInitParameter(TEST_INITPARAM));
+        assertNull(TEST_INITPARAM2 + " present before set ?", servletWrappingPortletContext.getInitParameter(TEST_INITPARAM));
+        servletWrappingPortletContext.log("Test logging call");
+        servletWrappingPortletContext.log("Test logging call", new Exception("Fake Exception"));
+        assertEquals("Default portlet context name not as expected ?", "MockServletContext", servletWrappingPortletContext.getPortletContextName());
+        assertFalse("Portlet runtime options not empty before set ?", servletWrappingPortletContext.getContainerRuntimeOptions().hasMoreElements());
+    }
+    // -------- Continue writing tests here --------------
+
+    class TestMockMultipartFile implements MultipartFile {
+        private String name;
+        private String originalFilename;
+        private String contentType;
+        private int size;
+        private byte[] bytes;
+
+        public TestMockMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
+            this.name = name;
+            this.originalFilename = originalFilename;
+            this.contentType = contentType;
+            if (bytes != null) {
+                this.bytes = Arrays.copyOf(bytes, bytes.length);
+            } else {
+                bytes = null;
+            }
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getOriginalFilename() {
+            return this.originalFilename;
+        }
+
+        @Override
+        public String getContentType() {
+            return contentType;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return (bytes != null ? bytes.length > 0 : true);
+        }
+
+        @Override
+        public long getSize() {
+            return (bytes != null ? bytes.length : 0);
+        }
+
+        @Override
+        public byte[] getBytes() throws IOException {
+            return bytes;
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return new ByteArrayInputStream(bytes);
+        }
+
+        @Override
+        public void transferTo(File file) throws IOException, IllegalStateException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+
+    class TetMockResourceBundle extends ResourceBundle {
+
+        @Override
+        protected Object handleGetObject(String key) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Enumeration<String> getKeys() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+
+    class TestMockPrincipal implements Principal {
+
+        @Override
+        public String getName() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public boolean implies(Subject subject) {
+            return Principal.super.implies(subject);
+        }
+    }
+}
diff --git a/plugins/junit-portlet/src/test/resources/applicationContext.xml b/plugins/junit-portlet/src/test/resources/applicationContext.xml
new file mode 100644
index 0000000..106fd6c
--- /dev/null
+++ b/plugins/junit-portlet/src/test/resources/applicationContext.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
+        <property name="scopes">
+            <map>
+                <entry key="session">
+                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
+                </entry>
+            </map>
+        </property>
+    </bean>
+
+</beans>
\ No newline at end of file
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 66bb555..fdee6ed 100644
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -45,6 +45,7 @@
         <module>jfreechart</module>
         <module>json</module>
         <module>junit</module>
+        <module>junit-portlet</module>
         <module>osgi</module>
         <module>oval</module>
         <module>pell-multipart</module>
diff --git a/plugins/portlet/pom.xml b/plugins/portlet/pom.xml
index c5198b8..5fc6480 100644
--- a/plugins/portlet/pom.xml
+++ b/plugins/portlet/pom.xml
@@ -42,6 +42,12 @@
 
         <dependency>
             <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-junit-portlet-plugin</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.struts</groupId>
             <artifactId>struts2-velocity-plugin</artifactId>
         </dependency>
 
diff --git a/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java b/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java
index 0c9801d..0f07ea9 100644
--- a/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java
+++ b/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java
@@ -23,11 +23,11 @@ import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
 import org.apache.struts2.portlet.PortletConstants;
 import org.apache.struts2.portlet.PortletPhase;
-import org.springframework.mock.web.portlet.MockPortletContext;
-import org.springframework.mock.web.portlet.MockPortletRequest;
-import org.springframework.mock.web.portlet.MockPortletResponse;
-import org.springframework.mock.web.portlet.MockPortletSession;
-import org.springframework.mock.web.portlet.MockStateAwareResponse;
+import org.apache.struts2.mock.web.portlet.MockPortletContext;
+import org.apache.struts2.mock.web.portlet.MockPortletRequest;
+import org.apache.struts2.mock.web.portlet.MockPortletResponse;
+import org.apache.struts2.mock.web.portlet.MockPortletSession;
+import org.apache.struts2.mock.web.portlet.MockStateAwareResponse;
 
 import javax.portlet.PortletMode;
 import java.util.HashMap;
diff --git a/plugins/portlet/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java b/plugins/portlet/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java
index df9dc10..ec13f12 100644
--- a/plugins/portlet/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java
+++ b/plugins/portlet/src/main/java/org/apache/struts2/portlet/PortletRequestMap.java
@@ -20,6 +20,7 @@ package org.apache.struts2.portlet;
 
 import javax.portlet.PortletRequest;
 import java.util.AbstractMap;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
@@ -49,9 +50,14 @@ public class PortletRequestMap extends AbstractMap<String, Object> {
     public void clear() {
         entries = null;
         Enumeration keys = request.getAttributeNames();
-
+        ArrayList<String> keyStrings = new ArrayList<>();
+        // Depending on the underlying collection, a ConcurrentModificationException may occur if we attempt
+        // to remove attributes while iterating over the keys Enumeration.  Instead use an interim collection to
+        // gather all the keys and then remove them.
         while (keys.hasMoreElements()) {
-            String key = (String) keys.nextElement();
+            keyStrings.add((String) keys.nextElement());
+        }
+        for (String key : keyStrings){
             request.removeAttribute(key);
         }
     }
diff --git a/plugins/portlet/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java b/plugins/portlet/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java
index 39c5125..0272043 100644
--- a/plugins/portlet/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java
+++ b/plugins/portlet/src/test/java/org/apache/struts2/components/PortletUrlRendererTest.java
@@ -24,9 +24,9 @@ import com.opensymphony.xwork2.util.ValueStack;
 import org.apache.struts2.StrutsStatics;
 import org.apache.struts2.StrutsTestCase;
 import org.apache.struts2.portlet.PortletConstants;
-import org.springframework.mock.web.portlet.MockMimeResponse;
-import org.springframework.mock.web.portlet.MockPortletContext;
-import org.springframework.mock.web.portlet.MockPortletRequest;
+import org.apache.struts2.mock.web.portlet.MockMimeResponse;
+import org.apache.struts2.mock.web.portlet.MockPortletContext;
+import org.apache.struts2.mock.web.portlet.MockPortletRequest;
 
 import javax.portlet.PortletContext;
 import javax.portlet.PortletMode;
diff --git a/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java b/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java
index 19d4c67..a282f2e 100644
--- a/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java
+++ b/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletRequestMapTest.java
@@ -25,7 +25,7 @@ import java.util.Set;
 import javax.portlet.PortletRequest;
 
 import org.jmock.MockObjectTestCase;
-import org.springframework.mock.web.portlet.MockPortletRequest;
+import org.apache.struts2.mock.web.portlet.MockPortletRequest;
 
 
 /**
diff --git a/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java b/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java
index d666cc6..a92c521 100644
--- a/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java
+++ b/plugins/portlet/src/test/java/org/apache/struts2/portlet/PortletSessionMapTest.java
@@ -26,7 +26,7 @@ import javax.portlet.PortletSession;
 
 import junit.framework.TestCase;
 
-import org.springframework.mock.web.portlet.MockPortletRequest;
+import org.apache.struts2.mock.web.portlet.MockPortletRequest;
 
 
 /**
diff --git a/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java b/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java
index be2ea35..0c54cf2 100644
--- a/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java
+++ b/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr168DispatcherTest.java
@@ -30,8 +30,8 @@ import org.easymock.EasyMock;
 import org.jmock.Mock;
 import org.jmock.cglib.MockObjectTestCase;
 import org.jmock.core.Constraint;
-import org.springframework.mock.web.portlet.MockPortletConfig;
-import org.springframework.mock.web.portlet.MockPortletContext;
+import org.apache.struts2.mock.web.portlet.MockPortletConfig;
+import org.apache.struts2.mock.web.portlet.MockPortletContext;
 
 import javax.portlet.*;
 import java.util.*;
diff --git a/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr286DispatcherTest.java b/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr286DispatcherTest.java
index c5f4346..843f4ce 100644
--- a/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr286DispatcherTest.java
+++ b/plugins/portlet/src/test/java/org/apache/struts2/portlet/dispatcher/Jsr286DispatcherTest.java
@@ -30,8 +30,8 @@ import org.easymock.EasyMock;
 import org.jmock.Mock;
 import org.jmock.cglib.MockObjectTestCase;
 import org.jmock.core.Constraint;
-import org.springframework.mock.web.portlet.MockPortletConfig;
-import org.springframework.mock.web.portlet.MockPortletContext;
+import org.apache.struts2.mock.web.portlet.MockPortletConfig;
+import org.apache.struts2.mock.web.portlet.MockPortletContext;
 
 import javax.portlet.*;
 import java.util.*;
diff --git a/pom.xml b/pom.xml
index b5786b7..2c83f31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -571,6 +571,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.struts</groupId>
+                <artifactId>struts2-junit-portlet-plugin</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.struts</groupId>
                 <artifactId>struts2-convention-plugin</artifactId>
                 <version>${project.version}</version>
             </dependency>