This blog entry shows how to test a flow with inheritance in Spring Webflow 2. The flow to be tested consists of a simple navigation which starts with a view state and ends getting to another view state that will depend on the result of the execution of a controller. This flow extends another flow which basically contains a redirection to a common page in case of error.
Introduction
The test flow (main.xml) is as follows:
<?xml version="1.0"
encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
parent="parent">
<view-state id="startPage"
view="main/startPage.jsp">
<transition on="next"
to="generateError"/>
</view-state>
<action-state id="generateError">
<evaluate expression="checkParameterController"/>
<transition on="ok"
to="OkView"/>
<transition on="ko"
to="KoView"/>
</action-state>
<end-state id="OkView"
view="main/finalOkView.jsp"/>
<end-state id="KoView"
view="main/finalKoView.jsp"/>
</flow>
And the parent flow (parent.xml):
<?xml version="1.0"
encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
abstract="true">
<end-state id="errorState"
view="commonErrorPage.jsp"/>
<global-transitions>
<transition on-exception="java.lang.Exception"
to="errorState"/>
</global-transitions>
</flow>
When executing tests, Spring provides us with a quite useful class: AbstractXmlFlowExecutionTests, in the org.springframework.webflow.test.execution package. This class has a variety of methods that will help us test the flow. The most interesting:
FlowDefinitionResource
getResource(FlowDefinitionResourceFactory resourceFactory)
Here, we specify where the flow which we want to test is located.
FlowDefinitionResource[] getModelResources(FlowDefinitionResourceFactory
resourceFactory)
If the flow uses inheritance, we define the parent flow here.
void
configureFlowBuilderContext(MockFlowBuilderContext builderContext)
Lets us customize the builder context. I use it for registering the beans that will use in the test as this is the way it's indicated at the class javadoc.
void
registerMockFlowBeans(ConfigurableBeanFactory flowBeanFactory)
Allows registering the beans that will be used by the flow to be tested. For example:
flowBeanFactory.registerSingleton("myService", new MyMockService());
Testing the flow
I've divided it in two classes in order to separate configuration from tests cases:
public class
BaseTestFlow extends
AbstractXmlFlowExecutionTests {
protected ApplicationContext
applicationContext;
/**
* This method returns
the flow to be tested.
*/
protected
FlowDefinitionResource getResource(FlowDefinitionResourceFactory
resourceFactory) {
return
resourceFactory.createFileResource("src/test/resources/flows/main.xml");
}
/**
* Needs to be overridden
if the flow to be tested inherits from another flow. In this case, we register
its parent flow.
*/
protected
FlowDefinitionResource[]
getModelResources(FlowDefinitionResourceFactory
resourceFactory) {
FlowDefinitionResource[]
flowDefinitionResources = new FlowDefinitionResource[1];
flowDefinitionResources[0] =
resourceFactory.createFileResource("src/test/resources/flows/parent.xml");
return
flowDefinitionResources;
}
/**
* Registers beans used
by the tested flow.
*/
protected void
configureFlowBuilderContext(MockFlowBuilderContext builderContext) {
applicationContext = new
ClassPathXmlApplicationContext(new String[] {
"classpath:xpadro/spring/test/configuration/root-context.xml",
"classpath:xpadro/spring/test/configuration/app-context.xml"
});
builderContext.registerBean("checkParameterController", applicationContext.getBean("checkParameterController"));
}
/*
protected void registerMockFlowBeans(ConfigurableBeanFactory
flowBeanFactory) {
flowBeanFactory.registerSingleton("checkParameterController",
new CheckParameterController());
}
*/
}
You could simply delete configureFlowBuilderContext method and use registerMockFlowBeans method instead if you don't want/need to start your own test context.
public class TestFlow extends BaseTestFlow {
public void
testFlowStarts() {
MockExternalContext
externalContext = new MockExternalContext();
startFlow(externalContext);
assertFlowExecutionActive();
assertCurrentStateEquals("startPage");
}
public void
testNavigation() {
MockExternalContext
externalContext = new MockExternalContext();
setCurrentState("startPage");
externalContext.setEventId("next");
externalContext.getMockRequestParameterMap().put("inputField", "yes");
getFlowScope().put("testFlowVar", "testValue");
resumeFlow(externalContext);
assertCurrentStateEquals("OkView");
assertEquals("testValue",
getFlowScope().get("testFlowVar"));
}
public void
testGlobalTransition() {
MockExternalContext
externalContext = new MockExternalContext();
setCurrentState("startPage");
externalContext.setEventId("next");
externalContext.getMockRequestParameterMap().put("inputField", "error");
resumeFlow(externalContext);
assertFlowExecutionOutcomeEquals("errorState");
}
}Labels: Spring, Test, Webflow