Announcement Announcement Module
Collapse
No announcement yet.
Subflow within a flow TEST: how to get something back Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Subflow within a flow TEST: how to get something back

    Hello all.

    I would like to use the return value of a subllow state to test (using 1.0-rc3) a parent flow. The phonebook sample shows how to test for subflow input, but, as I do not fully understand the AttributeMapper behaviour, I did not succeed to make a working thing with the subflow output.

    Here is where I stopped:

    Code:
        protected FlowServiceLocator createFlowServiceLocator() {
            MockFlowServiceLocator locator = new MockFlowServiceLocator();
            Flow authorize = new Flow("authorize");
            new EndState(authorize, "endSuccess");
            authorize.setOutputMapper(new AttributeMapper() {
                public void map(Object source, Object target, Map context) {
                    // what to do?
                }
            });
            locator.registerSubflow(authorize);
    ...
    Thank you for any help.

    By the way, I was not able to find the spring-binding Javadocs on line (had to download and read the code). Wouldn't it be a good idea to make it available?

    Thanks again,

    Mauricio
    ------------
    Visit www.helianto.org for a Spring-based experience...

  • #2
    Mauricio,

    Study closely what the Phonebook test is doing... in the "testSelectValidResult" method and associated mock detail flow setup.

    The search parent flow should pass a "id" input attribute to the "details" subflow. The attribute mapper simply verifies the id value that is expected is actually passed in.

    Yes, binding should be on-line. We'll correct this.
    HTH,

    Keith

    Comment


    • #3
      Relatedly....

      So,

      I've got a similar issue to what iserv is having. I want to test one of my flows that has a subflow. In my case, I would like to test the attribute-mapping for the subflow - input and output. I'm having some difficulty mocking up the subflow to accomodate this. Here's my flow xml:

      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE flow PUBLIC "-//SPRING//DTD WEBFLOW 1.0//EN" "http://www.springframework.org/dtd/spring-webflow-1.0.dtd">
      
      <flow start-state="start">
          <action-state id="start">
              <action bean="formAction" method="setupForm"/>
              <transition on="success" to="edit-construct-cassettes-subflow"/>
              <transition on="error" to="finish"/>
          </action-state>
      
          <subflow-state id="edit-construct-cassettes-subflow" flow="edit-construct-cassettes-subflow">
              <attribute-mapper>
                  <input-mapper>
                      <mapping source="flowScope.construct" target="construct"/>
                  </input-mapper>
                  <output-mapper>
                      <mapping source="flowScope.construct" target="construct"/>
                  </output-mapper>
              </attribute-mapper>
              <transition on="finish" to="saveConstruct"/>
              <transition on="cancel" to="finish"/>
          </subflow-state>
      
          <action-state id="saveConstruct">
              <action bean="formAction" method="saveConstruct"/>
              <transition on="success" to="finish"/>
              <transition on="error" to="edit-construct-cassettes-subflow"/>
          </action-state>
      
          <end-state id="finish" view="redirect:/app/view/construct.html?pk=${flowScope.construct.pk}"/>
      
          <import resource="edit-cassette-flow-context.xml"/>
      </flow>
      and here's where the test code stands right now:

      Code:
      public class EditConstructCassettesFlowExecutionTest extends AbstractXmlFlowExecutionTests {
          private ConstructService mockConstructService;
          private MockExternalContext externalContext;
          private Construct construct;
      
          @Override
          protected void setUp() {
              this.mockConstructService = createMock(ConstructService.class);
      
              // Create a test construct with map
              this.construct = new Construct();
              this.construct.setVersionNotes("some test notes");
              this.construct.setFunctionalDescription("some test description");
      
              // Setup the initial parameters for the workflow
              Map<String, String> params = new HashMap<String, String>();
              params.put("pk", "1");
              this.externalContext = new MockExternalContext(new ParameterMap(params));
      
              // program the mocks
              expect(this.mockConstructService.getConstruct(1)).andReturn(this.construct);
              this.mockConstructService.saveMap(this.construct);
      
              // flip the mocks to mock state
              replay(this.mockConstructService);
          }
      
      
          @Override
          protected FlowServiceLocator createFlowServiceLocator() {
              Flow subflow = new Flow("edit-construct-cassettes-subflow");
              subflow.addVariable(new SimpleFlowVariable("construct", Construct.class));
              EndState end = new EndState(subflow, "finish");
              end.setOutputMapper(new AttributeMapper() {public void map(Object arg0, Object arg1, Map arg2) {
                  // erm?
              };});
              subflow.setStartState(end);
      
              MockFlowServiceLocator flowArtifactFactory = new MockFlowServiceLocator();
              flowArtifactFactory.registerBean("constructService", this.mockConstructService);
              flowArtifactFactory.registerBean("editConstructService", new EditConstructServiceImpl());
              flowArtifactFactory.registerBean("constructValidator", new ConstructValidator());
              flowArtifactFactory.registerBean("viewListService", new ViewListServiceImpl());
              flowArtifactFactory.registerSubflow(subflow);
              return flowArtifactFactory;
          }
      
      
          @Override
          protected ExternalizedFlowDefinition getFlowDefinition() {
              File flowDir = new File("src/web/WEB-INF/flows");
              FileSystemResource resource = new FileSystemResource(new File(flowDir, "edit-construct-cassettes-flow.xml"));
              return new ExternalizedFlowDefinition(resource);
          }
      
      
          public void testStartFlow() {
              startFlow(new AttributeMap(), this.externalContext, null);
              assertFlowExecutionEnded();
              verify(this.mockConstructService);
          }
      }
      And what I get when I run it is:

      Code:
      org.springframework.binding.expression.EvaluationException: Expression [EvaluationAttempt@136a1a1 expression = [OgnlExpression@5f2db0 expression = flowScope.construct], target = map[[empty]], context = map[[empty]]] failed - make sure the expression is evaluatable on the target object; nested exception is ognl.OgnlException: source is null for getProperty(null, "construct")
      Caused by: ognl.OgnlException: source is null for getProperty(null, "construct")
      	at ognl.OgnlRuntime.getProperty(OgnlRuntime.java:1611)
      	at ognl.ASTProperty.getValueBody(ASTProperty.java:96)
      	at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
      	at ognl.SimpleNode.getValue(SimpleNode.java:210)
      	at ognl.ASTChain.getValueBody(ASTChain.java:109)
      	at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
      	at ognl.SimpleNode.getValue(SimpleNode.java:210)
      	at ognl.Ognl.getValue(Ognl.java:333)
      	at ognl.Ognl.getValue(Ognl.java:310)
      	at org.springframework.binding.expression.support.OgnlExpression.evaluateAgainst(OgnlExpression.java:60)
      	at org.springframework.binding.mapping.Mapping.map(Mapping.java:79)
      	at org.springframework.binding.mapping.DefaultAttributeMapper.map(DefaultAttributeMapper.java:79)
      	at org.springframework.webflow.support.AbstractFlowAttributeMapper.mapFlowOutput(AbstractFlowAttributeMapper.java:68)
      	at org.springframework.webflow.SubflowState.mapSubflowOutput(SubflowState.java:160)
      	at org.springframework.webflow.SubflowState.onEvent(SubflowState.java:146)
      	at org.springframework.webflow.Flow.onEvent(Flow.java:603)
      	at org.springframework.webflow.execution.impl.RequestControlContextImpl.signalEvent(RequestControlContextImpl.java:199)
      	at org.springframework.webflow.EndState.doEnter(EndState.java:140)
      	at org.springframework.webflow.State.enter(State.java:194)
      	at org.springframework.webflow.Flow.start(Flow.java:588)
      	at org.springframework.webflow.execution.impl.RequestControlContextImpl.start(RequestControlContextImpl.java:187)
      	at org.springframework.webflow.SubflowState.doEnter(SubflowState.java:116)
      	at org.springframework.webflow.State.enter(State.java:194)
      	at org.springframework.webflow.Transition.execute(Transition.java:220)
      	at org.springframework.webflow.TransitionableState.onEvent(TransitionableState.java:102)
      	at org.springframework.webflow.Flow.onEvent(Flow.java:603)
      	at org.springframework.webflow.execution.impl.RequestControlContextImpl.signalEvent(RequestControlContextImpl.java:199)
      	at org.springframework.webflow.ActionState.doEnter(ActionState.java:180)
      	at org.springframework.webflow.State.enter(State.java:194)
      	at org.springframework.webflow.Flow.start(Flow.java:588)
      	at org.springframework.webflow.execution.impl.RequestControlContextImpl.start(RequestControlContextImpl.java:187)
      	at org.springframework.webflow.execution.impl.FlowExecutionImpl.start(FlowExecutionImpl.java:170)
      	at org.springframework.webflow.test.AbstractFlowExecutionTests.startFlow(AbstractFlowExecutionTests.java:182)
      	at com.syngenta.genie.core.construct.flow.EditConstructCassettesFlowExecutionTest.testStartFlow(EditConstructCassettesFlowExecutionTest.java:130)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      	at java.lang.reflect.Method.invoke(Method.java:585)
      	at junit.framework.TestCase.runTest(TestCase.java:154)
      	at junit.framework.TestCase.runBare(TestCase.java:127)
      	at junit.framework.TestResult$1.protect(TestResult.java:106)
      	at junit.framework.TestResult.runProtected(TestResult.java:124)
      	at junit.framework.TestResult.run(TestResult.java:109)
      	at junit.framework.TestCase.run(TestCase.java:118)
      	at junit.framework.TestSuite.runTest(TestSuite.java:208)
      	at junit.framework.TestSuite.run(TestSuite.java:203)
      	at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:128)
      	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
      I think webflow is trying to find the "construct" property in flowScope for the subflow and is blowing up? So, how do I get things setup in the tests to do this?

      Thanks,
      Bubba

      Comment


      • #4
        Bubba,

        Your test is catching a bug in your flow definition. The bug is in the output-mapper definition of your subflow-state. The output mapper's source and target is wrong. For an output mapper, the source is a 'map' (the subflow output attribute map) and the target is the request context (of which flowScope points to the parent flow).

        See the DTD docs for more information.

        Keith

        Comment


        • #5
          Took a bit of hunting, but I got it to work. I wanted to share the example in case it would be of assistance with anyone else:

          Setting up the stub subflow:
          Code:
              protected FlowServiceLocator createFlowServiceLocator() {
                  Flow subflow = new Flow("edit-construct-cassettes-subflow");
                  subflow.setInputMapper(
                          new AttributeMapper() {
                      public void map(Object source, Object target, Map mappingContext) {
                          assertEquals("construct parameter not provided as input by calling flow", construct,
                                  ((AttributeMap) source).get("construct"));
                          ((RequestControlContext) target).getFlowScope().put("construct", construct);
                      }
                  });
                  EndState end = new EndState(subflow, "finish");
                  end.setOutputMapper(
                          new AttributeMapper() {
                      public void map(Object source, Object target, Map mappingContext) {
                          assertEquals("Expected construct to be passed in for output mapping, but it was not", construct,
                                  ((RequestControlContext) source).getFlowScope().get("construct"));
                          ((AttributeMap) target).put("construct", new ConstructForm(construct));
                      }
                      ;
                  });
                  subflow.setStartState(end);
          
                  MockFlowServiceLocator flowArtifactFactory = new MockFlowServiceLocator();
                  // register service beans needed for actions ... etc
                  flowArtifactFactory.registerSubflow(subflow);
                  return flowArtifactFactory;
              }
          The flow being tested:

          Code:
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE flow PUBLIC "-//SPRING//DTD WEBFLOW 1.0//EN" "http://www.springframework.org/dtd/spring-webflow-1.0.dtd">
          
          <flow start-state="start">
              <action-state id="start">
                  <action bean="formAction" method="setupForm"/>
                  <transition on="success" to="edit-construct-cassettes-subflow"/>
                  <transition on="error" to="finish"/>
              </action-state>
          
              <subflow-state id="edit-construct-cassettes-subflow" flow="edit-construct-cassettes-subflow">
                  <attribute-mapper>
                      <input-mapper>
                          <mapping source="flowScope.construct" target="construct"/>
                      </input-mapper>
                      <output-mapper>
                          <mapping source="construct" target="flowScope.construct"/>
                      </output-mapper>
                  </attribute-mapper>
                  <transition on="finish" to="saveConstruct"/>
                  <transition on="cancel" to="finish"/>
              </subflow-state>
          
              <action-state id="saveConstruct">
                  <action bean="formAction" method="saveConstruct"/>
                  <transition on="success" to="finish"/>
                  <transition on="error" to="edit-construct-cassettes-subflow"/>
              </action-state>
          
              <end-state id="finish" view="redirect:/app/view/construct.html?pk=${flowScope.construct.pk}"/>
          
              <import resource="edit-cassette-flow-context.xml"/>
          </flow>
          And relatedly, here is the actual subflow XML (not used in this test, but certainly used in the app):

          Code:
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE flow PUBLIC "-//SPRING//DTD WEBFLOW 1.0//EN" "http://www.springframework.org/dtd/spring-webflow-1.0.dtd">
          
          <flow start-state="editConstructCassettes">
              <input-mapper>
                <mapping source="construct" target="flowScope.construct"/>
              </input-mapper>
          
              <view-state id="editConstructCassettes" view="qc/construct/editCassettes">
                  <entry-actions>
                      <action bean="formAction" method="setupReferenceData"/>
                  </entry-actions>
                  <transition on="submit" to="finish">
                      <action bean="formAction" method="bindAndValidate"/>
                  </transition>
                  <transition on="delete" to="deleteCassette"/>
                  <transition on="complement" to="complementCassette"/>
                  <transition on="addCassette" to="addCassette">
                      <action bean="formAction" method="bind"/>
                  </transition>
                  <transition on="cancel" to="cancel"/>
              </view-state>
          
              <action-state id="addCassette">
                  <action bean="formAction" method="addCassette"/>
                  <transition on="success" to="editConstructCassettes"/>
                  <transition on="error" to="editConstructCassettes"/>
              </action-state>
              
              <action-state id="complementCassette">
                  <action bean="formAction" method="complement"/>
                  <transition on="success" to="editConstructCassettes"/>
                  <transition on="error" to="editConstructCassettes"/>
              </action-state>
          
              <action-state id="deleteCassette">
                  <action bean="formAction" method="delete"/>
                  <transition on="success" to="editConstructCassettes"/>
                  <transition on="error" to="editConstructCassettes"/>
              </action-state>
          
              <end-state id="cancel">
                  <output-mapper><mapping source="flowScope.construct" target="construct"/></output-mapper>
              </end-state>
          
              <end-state id="finish" view="redirect:/app/view/construct.html?pk=${flowScope.construct.pk}">
                  <output-mapper><mapping source="flowScope.construct" target="construct"/></output-mapper>
              </end-state>
          
              <import resource="edit-cassette-flow-context.xml"/>
          </flow>
          For reference sake, the itemlist sample in the webflow sources was useful, as well as the sample tests for the phonebook sample.

          It really helped me figure out the mappings to draw some boxes for the parent flow's subflow state and the child-flow's end-state mappers. When I realized that the mappers are mapping not from parent/child and vice-versa but instead to a temporary mapping-space it all made a lot more sense. Maybe I have a bit of a visual crutch here, but it's difficult for me to visualize the concepts from just the XML. When I read the descriptions about mapping attributes down to the subflow and mapping the output back into the parent flow I skip over those necessary "boxes" representing the transisionary mapping space. (I realize in hindsight that careful reading in the DTD comments would have aided me here - but it is not apparent in the actual produced XML)

          I hope that makes sense. I think what I'm trying to express is that a picture would help enormously with explaining to poor schmucks like me how to get the mappers configured correctly. I'm a lousy illustrator, otherwise I really would make the diagram and post it (somewhere).

          Comment


          • #6
            Bubba Puryear; you might find the following useful: http://blogs.warwick.ac.uk/colinyate..._web_flow_1_2/

            Comment


            • #7
              Bubba,

              Nice summary.

              Indeed. Pictures are important. We've inclued several in the reference docs, but yes, there needs to be some to illustrate attribute mapping.

              Contributions welcome :-)

              Comment


              • #8
                Yup

                Very useful indeed! It would be great for that to get slurped into the web-flow ref guide. (to put into Colin-speak)

                Comment


                • #9
                  Originally posted by Bubba Puryear
                  Very useful indeed! It would be great for that to get slurped into the web-flow ref guide. (to put into Colin-speak)
                  Keith; help yourself, but do it quickly; I do not know how long that blog will be around for now I no longer work there....

                  Comment


                  • #10
                    Bubba Puryear; I forgot to mention before, there is a book called "Expert Spring MVC + Web Flow" which you might find helpful.

                    In the interests of openness I should tell you that I wrote the Spring Web Flow chapters, and it is also quite out of date in terms of code listings etc., but still very relevant and I think you would find it useful

                    Comment

                    Working...
                    X