added throws IOException to View

added test support for asserting response written
This commit is contained in:
Keith Donald
2008-03-11 21:46:53 +00:00
parent ab39879ea5
commit f498616fd1
10 changed files with 106 additions and 67 deletions

View File

@@ -73,7 +73,7 @@ public class JsfView implements View {
/**
* This implementation performs the standard duties of the JSF RENDER_RESPONSE phase.
*/
public void render() {
public void render() throws IOException {
FacesContext facesContext = createFlowFacesContext();
facesContext.setViewRoot(viewRoot);
facesContext.renderResponse();

View File

@@ -81,7 +81,7 @@ public class JsfViewTests extends TestCase {
jsfMock.tearDown();
}
public final void testRender() {
public final void testRender() throws IOException {
EasyMock.expect(requestContext.getExternalContext()).andStubReturn(new MockExternalContext());
EasyMock.expect(requestContext.getFlashScope()).andStubReturn(flashMap);
@@ -99,7 +99,7 @@ public class JsfViewTests extends TestCase {
assertNull("The FacesContext was not released", FacesContext.getCurrentInstance());
}
public final void testRenderException() {
public final void testRenderException() throws IOException {
EasyMock.expect(requestContext.getExternalContext()).andStubReturn(new MockExternalContext());
EasyMock.expect(requestContext.getFlashScope()).andStubReturn(flashMap);

View File

@@ -0,0 +1,24 @@
package org.springframework.webflow.engine;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.View;
/**
* Thrown if a IO exception was thrown during view rendering.
*
* @author Keith Donald
*/
public class ViewRenderingException extends FlowExecutionException {
/**
* Create a new action execution exception.
* @param flowId the current flow
* @param stateId the current state (may be null)
* @param view the view that generated an unrecoverable exception
* @param cause the underlying cause
*/
public ViewRenderingException(String flowId, String stateId, View view, Throwable cause) {
super(flowId, stateId, "Exception thrown rendering " + view + " in state '" + stateId + "' of flow '" + flowId
+ "'", cause);
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.webflow.engine;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -214,14 +215,18 @@ public class ViewState extends TransitionableState {
}
}
private void render(RequestControlContext context, View view) {
private void render(RequestControlContext context, View view) throws ViewRenderingException {
if (logger.isDebugEnabled()) {
logger.debug("Rendering + " + view);
logger.debug(" Flash scope = " + context.getFlashScope());
logger.debug(" Messages = " + context.getMessageContext());
}
renderActionList.execute(context);
view.render();
try {
view.render();
} catch (IOException e) {
throw new ViewRenderingException(getOwner().getId(), getId(), view, e);
}
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.webflow.execution;
import java.io.IOException;
/**
* Allows the client to participate in flow execution. Encapsulates behavior to send the client an appropriate response
* and handle the resulting event once the client responds.
@@ -31,8 +33,9 @@ public interface View {
/**
* Render this view's content.
* @throws IOException if an IO Exception occured rendering the view
*/
public void render();
public void render() throws IOException;
/**
* Was a user event signaled on this view in this request?

View File

@@ -256,6 +256,15 @@ public class MockExternalContext implements ExternalContext {
getMockRequestParameterMap().put(parameterName, parameterValues);
}
/**
* Sets the id of the event that should be signaled by this context. For use when resuming a flow. This method
* depends on a MockViewFactory being configured for parsing the event id on a resume operation.
* @param eventId the id of the event to signal
*/
public void setEventId(String eventId) {
putRequestParameter("_eventId", eventId);
}
/**
* Set whether this request is an ajax request.
* @param ajaxRequest true or false
@@ -264,6 +273,14 @@ public class MockExternalContext implements ExternalContext {
this.ajaxRequest = ajaxRequest;
}
/**
* Returns the implementation of this mock context's response writer.
* @return the underlying string writer to use for asserting a specific response was written
*/
public StringWriter getMockResponseWriter() {
return responseWriter;
}
/**
* Returns the flag indicating if a flow execution redirect response has been requested by the flow.
*/

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.webflow.test;
import java.io.IOException;
import org.springframework.binding.expression.Expression;
import org.springframework.core.io.ResourceLoader;
import org.springframework.webflow.action.ViewFactoryActionAdapter;
@@ -102,8 +104,8 @@ class MockViewFactoryCreator implements ViewFactoryCreator {
return new Event(this, context.getRequestParameters().get("_eventId"));
}
public void render() {
// nothing to do
public void render() throws IOException {
context.getExternalContext().getResponseWriter().append(viewId);
}
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.test.MockExternalContext;
import org.springframework.webflow.test.MockParameterMap;
/**
* Base class for integration tests that verify a flow executes as expected. Flow execution tests captured by subclasses
@@ -119,45 +118,6 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
flowExecution.resume(context);
}
/**
* Signal the event against the paused flow execution. The event id will be translated into a Event object by the
* configured view factory and raised as an Event against the current view state. The event will cause the flow to
* change states if it matches a transition.
* @param eventId the event identifier
*/
protected void signalEvent(String eventId) {
MockExternalContext context = new MockExternalContext();
context.putRequestParameter("_eventId", eventId);
resumeFlow(context);
}
/**
* Signal the event against the paused flow execution. The event id will be translated into a Event object by the
* configured view factory and raised as an Event against the current view state. The event will cause the flow to
* change states if it matches a transition.
* @param eventId the event identifier
* @param input event input parameters
*/
protected void signalEvent(String eventId, MockParameterMap input) {
MockExternalContext context = new MockExternalContext(input);
context.putRequestParameter("_eventId", eventId);
resumeFlow(context);
}
/**
* Signal the event against the paused flow execution. The event id will be translated into a Event object by the
* configured view factory and raised as an Event against the current view state. The event will cause the flow to
* change states if it matches a transition.
* @param eventId the event identifier
* @param parameterName the name of the parameter
* @param parameterValue the value of the parameter
*/
protected void signalEvent(String eventId, String parameterName, String parameterValue) {
MockParameterMap input = new MockParameterMap();
input.put(parameterName, parameterValue);
signalEvent(eventId, input);
}
// convenience accessors
/**
@@ -268,6 +228,13 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
// assert helpers
/**
* Assert that the entire flow execution is active; that is, it has not ended and has been started.
*/
protected void assertFlowExecutionActive() {
assertTrue("The flow execution is not active but it should be", getFlowExecution().isActive());
}
/**
* Assert that the active flow session is for the flow with the provided id.
* @param expectedActiveFlowId the flow id that should have a session active in the tested flow execution
@@ -278,13 +245,6 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
getFlowExecution().getActiveSession().getDefinition().getId());
}
/**
* Assert that the entire flow execution is active; that is, it has not ended and has been started.
*/
protected void assertFlowExecutionActive() {
assertTrue("The flow execution is not active but it should be", getFlowExecution().isActive());
}
/**
* Assert that the entire flow execution has ended; that is, it is no longer active.
*/
@@ -303,6 +263,15 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
getFlowExecution().getActiveSession().getState().getId());
}
/**
* Assert that the response written to the mock context equals the response provided.
* @param response the expected response
* @param context the mock external context that was written to
*/
protected void assertResponseWrittenEquals(String response, MockExternalContext context) {
assertEquals(response, context.getMockResponseWriter().getBuffer().toString());
}
/**
* Factory method to create the flow execution factory. Subclasses could override this if they want to use a custom
* flow execution factory or custom configuration of the flow execution factory, registering flow execution

View File

@@ -38,7 +38,7 @@ public class MvcViewFactoryTests extends TestCase {
context = new StaticApplicationContext();
}
public void testNoResolversGetResource() {
public void testNoResolversGetResource() throws Exception {
creator.setApplicationContext(context);
ResourceLoader viewResourceLoader = new ResourceLoader() {
public ClassLoader getClassLoader() {
@@ -65,7 +65,7 @@ public class MvcViewFactoryTests extends TestCase {
assertEquals("/parent/myview.jsp", response.getForwardedUrl());
}
public void testViewResolversGetResource() {
public void testViewResolversGetResource() throws Exception {
MockViewResolver viewResolver = new MockViewResolver("myview");
creator.setApplicationContext(context);
creator.setViewResolvers(Collections.singletonList(viewResolver));
@@ -85,7 +85,7 @@ public class MvcViewFactoryTests extends TestCase {
assertEquals("myview", response.getForwardedUrl());
}
public void testRestoreView() {
public void testRestoreView() throws Exception {
creator.setApplicationContext(context);
ResourceLoader viewResourceLoader = new ResourceLoader() {
public ClassLoader getClassLoader() {
@@ -116,7 +116,7 @@ public class MvcViewFactoryTests extends TestCase {
assertEquals("/parent/myview.jsp", response.getForwardedUrl());
}
public void testRestoreViewButtonEventIdFormat() {
public void testRestoreViewButtonEventIdFormat() throws Exception {
creator.setApplicationContext(context);
ResourceLoader viewResourceLoader = new ResourceLoader() {
public ClassLoader getClassLoader() {

View File

@@ -45,25 +45,44 @@ public class SearchFlowExecutionTests extends AbstractXmlFlowExecutionTests {
public void testCriteriaSubmitSuccess() {
startFlow(new MockExternalContext());
MockParameterMap input = new MockParameterMap();
input.put("firstName", "Keith");
input.put("lastName", "Donald");
signalEvent("search", input);
MockExternalContext context = new MockExternalContext();
context.putRequestParameter("firstName", "Keith");
context.putRequestParameter("lastName", "Donald");
context.setEventId("search");
resumeFlow(context);
assertCurrentStateEquals("displayResults");
assertResponseWrittenEquals("searchResults", context);
}
public void testNewSearch() {
startFlow(new MockExternalContext());
signalEvent("search");
signalEvent("newSearch");
MockExternalContext context = new MockExternalContext();
context.putRequestParameter("firstName", "Keith");
context.putRequestParameter("lastName", "Donald");
context.setEventId("search");
resumeFlow(context);
context = new MockExternalContext();
context.setEventId("newSearch");
resumeFlow(context);
assertCurrentStateEquals("enterCriteria");
assertResponseWrittenEquals("searchCriteria", context);
}
public void testSelectValidResult() {
startFlow(new MockExternalContext());
signalEvent("search");
signalEvent("select", "id", "1");
MockExternalContext context = new MockExternalContext();
context.putRequestParameter("firstName", "Keith");
context.putRequestParameter("lastName", "Donald");
context.setEventId("search");
resumeFlow(context);
context = new MockExternalContext();
context.setEventId("select");
context.putRequestParameter("id", "1");
resumeFlow(context);
assertCurrentStateEquals("displayResults");
assertResponseWrittenEquals("searchResults", context);
}
protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) {