Announcement Announcement Module
Collapse
No announcement yet.
How do I set a variable in flow scope in a unit test? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How do I set a variable in flow scope in a unit test?

    Hi there.

    I've been facing this problem for a few days now and haven't been able to find a solution, not in the forums nor on the net or by playing around. I'm pretty sure that I'm just missing something trivial. But you know how it is...

    Anyway, the problem is the following part of my flow:
    Code:
    <view-state id="manager_welcome" view="/manager/manager_welcome">
            <on-entry>
                <evaluate expression="dataManager.getAppUser(currentUser)" result="flowScope.currentAppUser"/>
                <set name="flowScope.currentRole" value="currentAppUser.getAuthority()" />
            </on-entry>
    And this is the test for that part:
    Code:
    public void testStartManagerFlow() {            
            this.context.setCurrentUser(new PrincipalSpringSecurityUserToken(
                    "", "", "", new GrantedAuthority[]{new GrantedAuthorityImpl("user")}, 
                    "principal"));
            
            
            expect(this.dataManager.getAppUser((PrincipalSpringSecurityUserToken) this.context.getCurrentUser()))
                .andReturn(new User());
    
            replay(this.dataManager);
            startFlow(this.context);
            assertTrue(getRequiredFlowAttribute("dataManager") instanceof IDataManager);
            assertCurrentStateEquals("manager_welcome");
            
            verify(this.dataManager);
        }
    I have this method getAppUser(PrincipalSpringSecurityUserToken) that returns an object of type User. Now the test gives me an NPE because the variable currentAppUser is null. So far so good, I didn't set the variable so it has to be null.
    But how the heck do I set that variable? I'm really at loss here.

    Thanks for taking a glance

    theseion

  • #2
    What kind of unit test are you using? What is the context?

    Comment


    • #3
      Nevermind, I guess you are using someting like AbstractXmlFlowExecutionTests. Well, the currentAppUser variable should be set by your flow:

      <evaluate expression="dataManager.getAppUser(currentUser)" result="flowScope.currentAppUser"/>

      so it is not a matter of setting the variable. Are you sure where exactly you are getting the NPE?

      Comment


      • #4
        I'm indeed using AbstractXmlFlowExecutionTest.

        the currentAppUser variable should be set by your flow
        That's exactly what I thought. But since the DataManager object is a Mock object getAppUser() will not return anything on it's own (is that correct?).
        My understanding is that I tell the flow what the method returns by using andReturn() on the expected() method call like this:

        expect(this.dataManager.getUser(<currentUser>)).an dReturn(new User());

        But if that understanding was wrong I would have to set that variable in a different way, as far as I can see...

        The NPE is defenitely thrown because the variable currentAppUser is null.

        Thanks for you help nkaza.

        Comment


        • #5
          Yes, that's correct. That's how you setup mock object behavior in EasyMock.

          Now, I assume you have this code somewhere:

          Code:
          	@Override
          	protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) {
          	    builderContext.registerBean("dataManager", this.dataManager);
          	}
          and this:

          Code:
          this.dataManager = createStrictMock(IDataManager.class);

          I am not sure if this line makes sense:

          Code:
          assertTrue(getRequiredFlowAttribute("dataManager") instanceof DataManager);

          Other than that it seems correct. There shouldn't be anything else you need to do. If you still have problems, post the entire TestCase code and flow.

          Comment


          • #6
            I do have those lines in my code. I really don't see the problem so I'll post the code.

            manager-flow:
            Code:
            <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">
            
                <secured attributes="ROLE_SUPER_MANAGER, ROLE_SECTION_MANAGER" match="any"/>
                
                <var name="dataManager" class="ch.unibe.iam.applicationManager.service.DataManager" />
                
                <!-- 
                    view states
                 -->
            
                <view-state id="manager_welcome" view="/manager/manager_welcome">
                    <on-entry>
                        <evaluate expression="dataManager.getUser(currentUser)" result="flowScope.currentAppUser"/>
                        <set name="flowScope.currentRole" value="currentAppUser.getAuthority()" />
                        <set name="flowScope.currentState" value="flowExecutionContext.activeSession.state.id" />
                    </on-entry>
                    <on-render>
                        <evaluate expression="dataManager.getLatestDossiers(flowExecutionUrl, currentAppUser)" result="flowScope.latestDossiersByState"/>
                    </on-render>
                    <transition on="select_dossier_action" to="view_dossier">
                        <evaluate expression="dataManager.getDossier(requestParameters.dossier)" result="flowScope.currentDossier"/>
                    </transition>
                </view-state>
            
            ...
            test:
            Code:
            public class ManagerWelcomeTest extends AbstractXmlFlowExecutionTests{
            
                private IDataManager dataManager;
                private MockExternalContext context;
                
                 protected void setUp() {
                     this.context = new MockExternalContext();
                     this.dataManager = createStrictMock(IDataManager.class);
                 }
                 
                @Override
                protected FlowDefinitionResource getResource(
                        FlowDefinitionResourceFactory resourceFactory) {
                    return resourceFactory.createFileResource(
                            "src/main/webapp/WEB-INF/flows/manager-flow.xml");
                }
                
                @Override
                protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) {
                    builderContext.registerBean("dataManager", this.dataManager);
                }
            
                
                /*
                 * Test cases
                 */
                public void testStartManagerFlow() {
                    User user = new User();
            	user.setAuthority(Role.USER.getRoleName());
            		
                    PrincipalSpringSecurityUserToken authToken = new PrincipalSpringSecurityUserToken(
            		"key", "username", "password", new GrantedAuthority[]{
            		new GrantedAuthorityImpl(Role.USER.getRoleName())
            		}, user);
            		
                    this.context.setCurrentUser(authToken);
            		
            	expect(this.dataManager.getUser(authToken)).andReturn(user);
            	replay(this.dataManager);
            		
            	startFlow(this.context);
            	assertCurrentStateEquals("manager_welcome");
            	verify(this.dataManager);
                }
            
            ...
            The line causing the exception is this one in the flow:
            Code:
            <set name="flowScope.currentRole" value="currentAppUser.getAuthority()" />
            and this line in the test:
            Code:
            startFlow(this.context);

            That should be all of the relevant code. Please let me know if you need to see more.
            Last edited by theseion; May 10th, 2009, 02:31 PM.

            Comment


            • #7
              Ok just remove this line from the flow:

              Code:
              <var name="dataManager" class="ch.unibe.iam.applicationManager.service.DataManager" />
              The dataManager is registered in configureFlowBuilderContext. The var you have declared replaces it with a simple instance of DataManager.

              Comment


              • #8
                aaaah!
                That means though, that I'll have to have a "testable" version of my flows. Is that common practice?

                Again, thanks a lot for your help! Much appreciated.

                Comment


                • #9
                  Not really. The proper way is to declare the dataManager as a spring bean, not as a var. So the test case is just providing a mock implementation of the spring bean.

                  Comment


                  • #10
                    Well, I'd say there's definitely room for improvement on this matter. What's the use of such a construct if you can't have it in you test...

                    Comment


                    • #11
                      Originally posted by theseion View Post
                      Well, I'd say there's definitely room for improvement on this matter. What's the use of such a construct if you can't have it in you test...
                      Allow me to disagree on that. A var declared inside the flow is part of the flow being unit tested. So it doesn't make sense to replace it with something else. That would be analogous to unit testing a piece of code and wanting to replace its local variables during testing. Local variables are part of the code being tested. Unit testing is only supposed to provide different implementations of the flow's dependencies, not its internal variables.

                      Comment


                      • #12
                        I want to answer that later, not enough time right now. Just on a quick notice, I did find a solution to my original question in this thread: http://forum.springsource.org/showth...on-render+test

                        To set a flowScope variable, e.g. to test a specific state without going through all the other states, do the following:

                        Code:
                        setCurrentState("stateName"); //this configures the execution environment
                        getFlowScope().put("varName", someObject); //set the variable
                        One of my problems while playing around had been that I getFlowScope() would return null. The trick is to call setCurrentState() before that.

                        I'll be back later to answer you last post.

                        theseion

                        Comment


                        • #13
                          So, finally I found time to answer your last post.

                          I've thought about what you wrote and I agree. Still I find the current situation pretty annoying since every change to the flow not only requires a change to the tests but also an updated copy of the flow without the <var> declarations.

                          I don't really have an understanding of the internals of Webflow but maybe something like a test switch parameter to the <var> tag could be a solution. E.g.:
                          Code:
                          <var name="myClass" class="com.package.MyClass" mock="true" />
                          I'm not only sugesting this because I find it annoying to have a modified copy of the flow for the test but also because I found this setup do be error-prone: if I modify the flow, run the test but forgot to replace the test copy of the flow I will maybe discover any errors made later, once I do replace the test copy. But tracking a bug down at this point will be much more difficult since I was probably expecting problems in completely different area.

                          Sorry for keeping you waiting so long.

                          Cheers.

                          -theseion

                          Comment

                          Working...
                          X