diff --git a/spring-webflow/changelog.txt b/spring-webflow/changelog.txt
index 6b098924..b82ff864 100644
--- a/spring-webflow/changelog.txt
+++ b/spring-webflow/changelog.txt
@@ -12,9 +12,12 @@ Package org.springframework.webflow.engine
* Ensured the RequestContext attribute map can never be null (SWF-347).
Package org.springframework.webflow.execution
-* Made the Event class non-final to allow for extension (SWF-330).
+* Made the Event class non-final to allow for possibility of extension (SWF-330).
* Now allow multiple conversation containers per session in a manner that supports use in a cluster (SWF-378).
+Package org.springframework.webflow.test
+* Introduced MockAction for stubbing out action implementations when unit testing flow executions (SWF-317).
+
Package reference-manual
* Improved readability of reference manual in several sections (SWF-349).
* Added documentation on flow execution exception handling options (SWF-357).
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockAction.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockAction.java
new file mode 100644
index 00000000..04d66721
--- /dev/null
+++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockAction.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2004-2007 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
+ *
+ * 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.springframework.webflow.test;
+
+import org.springframework.webflow.core.collection.AttributeMap;
+import org.springframework.webflow.execution.Action;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.springframework.webflow.test.execution.AbstractExternalizedFlowExecutionTests;
+import org.springframework.webflow.test.execution.AbstractXmlFlowExecutionTests;
+
+/**
+ * A trivial stub action implementation that can be parameterized to return a particular action execution result. Useful
+ * for simulating different action results a flow should be capable of responding to.
+ *
+ * Instances of this class can be conveniently installed in the bean factory that hosts the Web Flow action
+ * implementations in a test environment by overriding registerMockServices on
+ * {@link AbstractExternalizedFlowExecutionTests}. If you are using a XML-based flow definition with a flow-local
+ * context to host your actions, consider overriding registerLocalMockServices on
+ * {@link AbstractXmlFlowExecutionTests} to install mock instances.
+ *
+ * @author Keith Donald
+ */
+public class MockAction implements Action {
+
+ private String resultEventId;
+
+ private AttributeMap resultAttributes;
+
+ /**
+ * Constructs a new mock action that returns the default success execution result.
+ */
+ public MockAction() {
+ setResultEventId("success");
+ }
+
+ /**
+ * Constructs a new mock action that returns the provided execution result.
+ * @param resultEventId the execution result identifier that will be returned
+ */
+ public MockAction(String resultEventId) {
+ setResultEventId(resultEventId);
+ }
+
+ /**
+ * Sets the event identifier this mock action will use as its execution outcome.
+ * @param resultEventId the action execution result identifier
+ */
+ public void setResultEventId(String resultEventId) {
+ this.resultEventId = resultEventId;
+ }
+
+ /**
+ * Sets attributes to associate with a returned action execution outcome.
+ * @param resultAttributes the action execution result attributes
+ */
+ public void setResultAttributes(AttributeMap resultAttributes) {
+ this.resultAttributes = resultAttributes;
+ }
+
+ public Event execute(RequestContext context) {
+ return new Event(this, resultEventId, resultAttributes);
+ }
+
+}
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/test/MockActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/test/MockActionTests.java
new file mode 100644
index 00000000..7b823361
--- /dev/null
+++ b/spring-webflow/src/test/java/org/springframework/webflow/test/MockActionTests.java
@@ -0,0 +1,34 @@
+package org.springframework.webflow.test;
+
+import junit.framework.TestCase;
+
+import org.springframework.webflow.core.collection.LocalAttributeMap;
+import org.springframework.webflow.execution.Event;
+
+public class MockActionTests extends TestCase {
+ public void testMockActionExecute() {
+ MockAction action = new MockAction();
+ Event e = action.execute(new MockRequestContext());
+ assertEquals("success", e.getId());
+ assertTrue(e.getAttributes().isEmpty());
+ }
+
+ public void testMockActionExecuteCustomResult() {
+ MockAction action = new MockAction("foo");
+ Event e = action.execute(new MockRequestContext());
+ assertEquals("foo", e.getId());
+ assertTrue(e.getAttributes().isEmpty());
+ }
+
+ public void testMockActionExecuteCustomResultAttributes() {
+ MockAction action = new MockAction("foo");
+ LocalAttributeMap resultAttributes = new LocalAttributeMap();
+ resultAttributes.put("bar", "baz");
+ action.setResultAttributes(resultAttributes);
+ Event e = action.execute(new MockRequestContext());
+ assertEquals("foo", e.getId());
+ assertFalse(e.getAttributes().isEmpty());
+ assertEquals(e.getAttributes().get("bar"), "baz");
+ }
+
+}