Struts 2
  1. Struts 2
  2. WW-3826

Unit Testing A Portlet Action Using StrutsTestCase Causes A NullPointerException

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.3.4
    • Fix Version/s: 2.3.16
    • Component/s: Plugin - Portlet
    • Labels:
      None
    • Environment:

      JUnit 4.8.2 - Struts version 2.3.4

      Description

      When running a unit test of a Struts portlet Action using StrutsTestCase the test causes an error -

      java.lang.NullPointerException at org.apache.struts2.portlet.interceptor.PortletStateInterceptor.intercept(PortletStateInterceptor.java:52)

      (see below for complete stack trace)

      The same test when run with Struts portlet plugin version 2.2.1 passes.

      You can download an example application here: http://www.brucephillips.name/struts/Struts2CRUDPortletExample_Finish.zip. Unzip the example. In the project's root folder run mvn -e clean test. The test will pass.

      Modify pom.xml in the project to set the version number of Struts to 2.3.4. Remove the dependency on javaassist. Save the pom.xml

      In the project's root folder run mvn -e clean test. The test will error. View the test report in target/surfire-reports.

      -------------------------------------------------------------------------------
      Test set: com.struts2.tutorial.action.ListEmployeeActionTest
      -------------------------------------------------------------------------------
      Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.185 sec <<< FAILURE!
      testExecute(com.struts2.tutorial.action.ListEmployeeActionTest) Time elapsed: 0.998 sec <<< ERROR!
      java.lang.NullPointerException
      at org.apache.struts2.portlet.interceptor.PortletStateInterceptor.intercept(PortletStateInterceptor.java:52)
      at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      at org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54)
      at com.struts2.tutorial.action.ListEmployeeActionTest.testExecute(ListEmployeeActionTest.java:24)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:601)
      at junit.framework.TestCase.runTest(TestCase.java:168)
      at junit.framework.TestCase.runBare(TestCase.java:134)
      at junit.framework.TestResult$1.protect(TestResult.java:110)
      at junit.framework.TestResult.runProtected(TestResult.java:128)
      at junit.framework.TestResult.run(TestResult.java:113)
      at junit.framework.TestCase.run(TestCase.java:124)
      at junit.framework.TestSuite.runTest(TestSuite.java:243)
      at junit.framework.TestSuite.run(TestSuite.java:238)
      at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
      at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:53)
      at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:123)
      at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:104)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:601)
      at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:164)
      at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:110)
      at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:175)
      at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcessWhenForked(SurefireStarter.java:107)
      at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:68)

        Activity

        Hide
        Michael Menzies added a comment -

        I ran into the same issue. I do believe that this is something that should be address... but there is a relatively easy workaround to get it to work.

        Basically it's a matter of mocking your PortletContext as needed. It's throwing a NPE in the PortletStateInterceptor because there is no phase defined in the context. There's a few other things that the context needs too. Here's my solution (using jMock to mock the PortletResponse):

            protected void prepareActionForTesting(final ActionProxy proxy) throws Exception
            {
            	final Mockery context = new Mockery();
                portletRequest = new MockPortletRequest();
                portletResponse = context.mock(ActionResponse.class);
        		context.checking(new Expectations() {
        			{
        				allowing(portletResponse).setRenderParameter(
        		    			with(any(String.class)),with(any(String.class)));
        				allowing(portletResponse).setPortletMode(
        		    			with(any(PortletMode.class)));
        			}
        		});
                portletSession = new MockPortletSession();
                portletRequest.setSession(portletSession);
        
                final ActionContext actionContext = proxy.getInvocation().getInvocationContext();
                actionContext.setSession(session);
                actionContext.put(PortletConstants.REQUEST,portletRequest);
                actionContext.put(PortletConstants.RESPONSE,portletResponse);
                actionContext.put(PortletConstants.MODE_NAMESPACE_MAP,new HashMap<PortletMode, String>());
                actionContext.put(PortletConstants.PHASE, PortletPhase.EVENT_PHASE);
            }
        
        Show
        Michael Menzies added a comment - I ran into the same issue. I do believe that this is something that should be address... but there is a relatively easy workaround to get it to work. Basically it's a matter of mocking your PortletContext as needed. It's throwing a NPE in the PortletStateInterceptor because there is no phase defined in the context. There's a few other things that the context needs too. Here's my solution (using jMock to mock the PortletResponse): protected void prepareActionForTesting( final ActionProxy proxy) throws Exception { final Mockery context = new Mockery(); portletRequest = new MockPortletRequest(); portletResponse = context.mock(ActionResponse.class); context.checking( new Expectations() { { allowing(portletResponse).setRenderParameter( with(any( String .class)),with(any( String .class))); allowing(portletResponse).setPortletMode( with(any(PortletMode.class))); } }); portletSession = new MockPortletSession(); portletRequest.setSession(portletSession); final ActionContext actionContext = proxy.getInvocation().getInvocationContext(); actionContext.setSession(session); actionContext.put(PortletConstants.REQUEST,portletRequest); actionContext.put(PortletConstants.RESPONSE,portletResponse); actionContext.put(PortletConstants.MODE_NAMESPACE_MAP, new HashMap<PortletMode, String >()); actionContext.put(PortletConstants.PHASE, PortletPhase.EVENT_PHASE); }
        Hide
        Michael Menzies added a comment - - edited

        The more permanent solution lies in properly setting up a MockPortletContext in the StrutsTestCase class. The problem I ran into with this is that my portlet app is using JSR-168, and the getMajorVersion() in MockPortletContext is hardcoded to return 2 (JSR-286).

        See the comments in this ticket for a bit more detail: https://issues.apache.org/jira/browse/WW-3815

        Show
        Michael Menzies added a comment - - edited The more permanent solution lies in properly setting up a MockPortletContext in the StrutsTestCase class. The problem I ran into with this is that my portlet app is using JSR-168, and the getMajorVersion() in MockPortletContext is hardcoded to return 2 (JSR-286). See the comments in this ticket for a bit more detail: https://issues.apache.org/jira/browse/WW-3815
        Hide
        Lukasz Lenart added a comment -

        What about creating a new class StrutsPortletTestCase and put it into struts2-portlet-plugin ? Then it can be used instead of StrutsTestCase for portlet actions. I cannot extend StrutsTestCase with struts2-portlet-plugin specific code as this will create cyclic dependencies.

        Show
        Lukasz Lenart added a comment - What about creating a new class StrutsPortletTestCase and put it into struts2-portlet-plugin ? Then it can be used instead of StrutsTestCase for portlet actions. I cannot extend StrutsTestCase with struts2-portlet-plugin specific code as this will create cyclic dependencies.
        Hide
        Bruce Phillips added a comment -

        Should StrutsPortletTestCase go in the struts2-junit-plugin along with the other Struts2 test classes?

        Show
        Bruce Phillips added a comment - Should StrutsPortletTestCase go in the struts2-junit-plugin along with the other Struts2 test classes?
        Hide
        Lukasz Lenart added a comment -

        Good idea, would be nice

        Show
        Lukasz Lenart added a comment - Good idea, would be nice
        Hide
        Bruce Phillips added a comment -

        Michael - I'm trying to implement your work-around for the NPE when unit testing a Struts 2 portlet action. Where do you declare the portletRequest, portletResponse, and portletSession objects referenced in the prepareActionForTesting method? Also is that method annotated with @Before so it runs before the method where I'm testing the portlet action?

        If you could can you email me (bphillips@ku.edu) a complete example Test class?

        We'd like to upgrade to 2.3.4 but have several unit tests that are throwing the NPE.

        Thanks for the help.

        Show
        Bruce Phillips added a comment - Michael - I'm trying to implement your work-around for the NPE when unit testing a Struts 2 portlet action. Where do you declare the portletRequest, portletResponse, and portletSession objects referenced in the prepareActionForTesting method? Also is that method annotated with @Before so it runs before the method where I'm testing the portlet action? If you could can you email me (bphillips@ku.edu) a complete example Test class? We'd like to upgrade to 2.3.4 but have several unit tests that are throwing the NPE. Thanks for the help.
        Hide
        Lukasz Lenart added a comment -

        ech... struts2-portlet-plugin already depends on struts2-junit-plugin so I cannot add struts2-portlet-plugin as dependency for struts2-junit-plugin

        Any idea how to solve that ? I thought about defining a new plugin - struts2-portlet-testing-plugin, any other ideas ?

        Show
        Lukasz Lenart added a comment - ech... struts2-portlet-plugin already depends on struts2-junit-plugin so I cannot add struts2-portlet-plugin as dependency for struts2-junit-plugin Any idea how to solve that ? I thought about defining a new plugin - struts2-portlet-testing-plugin, any other ideas ?
        Hide
        Bruce Phillips added a comment -

        Why should struts2-portlet-plugin have a dependency to struts2-junit-plugin? struts2-core doesn't have such a dependency. I wonder what breaks if you remove that dependency?

        Show
        Bruce Phillips added a comment - Why should struts2-portlet-plugin have a dependency to struts2-junit-plugin? struts2-core doesn't have such a dependency. I wonder what breaks if you remove that dependency?
        Hide
        Bruce Phillips added a comment -

        Michael - I think I figured out how to use the work-around you wrote about above - here is my complete test class (this class tests a portlet action in the example project I posted a link to in my original bug report).

        Let me know if the below is not correct. My test does pass now.

        package com.struts2.tutorial.action;

        import java.util.HashMap;
        import java.util.List;
        import java.util.Map;
        import javax.portlet.ActionResponse;
        import javax.portlet.PortletMode;
        import org.apache.log4j.Logger;
        import org.apache.struts2.StrutsTestCase;
        import org.apache.struts2.portlet.PortletConstants;
        import org.apache.struts2.portlet.PortletPhase;
        import org.jmock.Expectations;
        import org.jmock.Mockery;
        import org.junit.Test;
        import org.springframework.mock.web.portlet.MockPortletRequest;
        import org.springframework.mock.web.portlet.MockPortletSession;
        import com.opensymphony.xwork2.ActionContext;
        import com.opensymphony.xwork2.ActionProxy;
        import com.struts2.tutorial.model.Employee;

        public class ListEmployeeActionTest extends StrutsTestCase {

        private final static Logger LOG = Logger.getLogger(ListEmployeeActionTest.class.getName());

        MockPortletRequest portletRequest ;

        ActionResponse portletResponse ;

        MockPortletSession portletSession ;

        Map<String,Object> session = new HashMap<String, Object>();

        protected void prepareActionForTesting(final ActionProxy proxy) throws Exception
        {
        final Mockery context = new Mockery();
        portletRequest = new MockPortletRequest();
        portletResponse = context.mock(ActionResponse.class);
        context.checking(new Expectations() {

        { allowing(portletResponse).setRenderParameter( with(any(String.class)),with(any(String.class))); allowing(portletResponse).setPortletMode( with(any(PortletMode.class))); }

        });
        portletSession = new MockPortletSession();
        portletRequest.setSession(portletSession);

        final ActionContext actionContext = proxy.getInvocation().getInvocationContext();
        actionContext.setSession(session);
        actionContext.put(PortletConstants.REQUEST,portletRequest);
        actionContext.put(PortletConstants.RESPONSE,portletResponse);
        actionContext.put(PortletConstants.MODE_NAMESPACE_MAP,new HashMap<PortletMode, String>());
        actionContext.put(PortletConstants.PHASE, PortletPhase.EVENT_PHASE);
        }

        @Test
        public void testExecute() throws Exception

        { ActionProxy proxy = getActionProxy("/view/index.action"); prepareActionForTesting(proxy); ListEmployeeAction action = (ListEmployeeAction) proxy.getAction() ; assertNotNull(action); String result = proxy.execute() ; assertEquals("Result of calling execute method of index action is not success but it should be.", "success", result); List<Employee> employees = action.getEmployees() ; LOG.info("Number of employees found " + employees.size() ); assertEquals("Number of employees found is not 2 but should be.", 2, employees.size() ); }

        }

        Show
        Bruce Phillips added a comment - Michael - I think I figured out how to use the work-around you wrote about above - here is my complete test class (this class tests a portlet action in the example project I posted a link to in my original bug report). Let me know if the below is not correct. My test does pass now. package com.struts2.tutorial.action; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.portlet.ActionResponse; import javax.portlet.PortletMode; import org.apache.log4j.Logger; import org.apache.struts2.StrutsTestCase; import org.apache.struts2.portlet.PortletConstants; import org.apache.struts2.portlet.PortletPhase; import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.Test; import org.springframework.mock.web.portlet.MockPortletRequest; import org.springframework.mock.web.portlet.MockPortletSession; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionProxy; import com.struts2.tutorial.model.Employee; public class ListEmployeeActionTest extends StrutsTestCase { private final static Logger LOG = Logger.getLogger(ListEmployeeActionTest.class.getName()); MockPortletRequest portletRequest ; ActionResponse portletResponse ; MockPortletSession portletSession ; Map<String,Object> session = new HashMap<String, Object>(); protected void prepareActionForTesting(final ActionProxy proxy) throws Exception { final Mockery context = new Mockery(); portletRequest = new MockPortletRequest(); portletResponse = context.mock(ActionResponse.class); context.checking(new Expectations() { { allowing(portletResponse).setRenderParameter( with(any(String.class)),with(any(String.class))); allowing(portletResponse).setPortletMode( with(any(PortletMode.class))); } }); portletSession = new MockPortletSession(); portletRequest.setSession(portletSession); final ActionContext actionContext = proxy.getInvocation().getInvocationContext(); actionContext.setSession(session); actionContext.put(PortletConstants.REQUEST,portletRequest); actionContext.put(PortletConstants.RESPONSE,portletResponse); actionContext.put(PortletConstants.MODE_NAMESPACE_MAP,new HashMap<PortletMode, String>()); actionContext.put(PortletConstants.PHASE, PortletPhase.EVENT_PHASE); } @Test public void testExecute() throws Exception { ActionProxy proxy = getActionProxy("/view/index.action"); prepareActionForTesting(proxy); ListEmployeeAction action = (ListEmployeeAction) proxy.getAction() ; assertNotNull(action); String result = proxy.execute() ; assertEquals("Result of calling execute method of index action is not success but it should be.", "success", result); List<Employee> employees = action.getEmployees() ; LOG.info("Number of employees found " + employees.size() ); assertEquals("Number of employees found is not 2 but should be.", 2, employees.size() ); } }
        Hide
        Lukasz Lenart added a comment -

        PortletUrlRendererTest from struts2-portlet-plugin extends StrutsTestCase from struts2-junit-plugin

        Show
        Lukasz Lenart added a comment - PortletUrlRendererTest from struts2-portlet-plugin extends StrutsTestCase from struts2-junit-plugin
        Hide
        Michael Menzies added a comment -

        Yup, looks good to me. Glad you got it working, I was about to send you an email.

        Show
        Michael Menzies added a comment - Yup, looks good to me. Glad you got it working, I was about to send you an email.
        Hide
        Michael Menzies added a comment -

        I would like to get rid of the jmock-ed object though. It was a quick fix to get my tests working again, but it could be done without it I'm sure. It's probably an unnecessary dependency.

        Show
        Michael Menzies added a comment - I would like to get rid of the jmock-ed object though. It was a quick fix to get my tests working again, but it could be done without it I'm sure. It's probably an unnecessary dependency.
        Hide
        Lukasz Lenart added a comment -

        Another idea, add StrutsPortletTestCase as a regular class in struts2-portlet-plugin, the only problem is that a test class will be deployed to production. The same situation is with XWorkTestCase.

        Show
        Lukasz Lenart added a comment - Another idea, add StrutsPortletTestCase as a regular class in struts2-portlet-plugin, the only problem is that a test class will be deployed to production. The same situation is with XWorkTestCase.
        Hide
        Lukasz Lenart added a comment -

        I've added a new class - StrutsPortletTestCase, it allows to test portlet actions. It bases on Michael's code, thanks a lot!

        Just add

        extends StrutsPortletTestCase

        into your tests and should work! Please test with the latest snapshot (when Hudson comment here).

        Show
        Lukasz Lenart added a comment - I've added a new class - StrutsPortletTestCase, it allows to test portlet actions. It bases on Michael's code, thanks a lot! Just add extends StrutsPortletTestCase into your tests and should work! Please test with the latest snapshot (when Hudson comment here).
        Hide
        Lukasz Lenart added a comment -

        Docs must be updated as well ...

        Show
        Lukasz Lenart added a comment - Docs must be updated as well ...
        Hide
        Hudson added a comment -

        Integrated in Struts2 #484 (See https://builds.apache.org/job/Struts2/484/)
        WW-3826 adds new StrutsPortletTestCase to simplify testing actions to be used in portlet environment (Revision 1344572)

        Result = SUCCESS
        lukaszlenart :
        Files :

        • /struts/struts2/trunk/plugins/junit/src/main/java/org/apache/struts2/StrutsTestCase.java
        • /struts/struts2/trunk/plugins/portlet/pom.xml
        • /struts/struts2/trunk/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java
        Show
        Hudson added a comment - Integrated in Struts2 #484 (See https://builds.apache.org/job/Struts2/484/ ) WW-3826 adds new StrutsPortletTestCase to simplify testing actions to be used in portlet environment (Revision 1344572) Result = SUCCESS lukaszlenart : Files : /struts/struts2/trunk/plugins/junit/src/main/java/org/apache/struts2/StrutsTestCase.java /struts/struts2/trunk/plugins/portlet/pom.xml /struts/struts2/trunk/plugins/portlet/src/main/java/org/apache/struts2/StrutsPortletTestCase.java
        Hide
        Bruce Phillips added a comment -

        The test of the portlet action now passes when extending StrutsPortletTestCase.

        For those portlet action classes that use Spring and the Struts Spring plugin do we need a StrutsPortletSpringTestCase that extends StrutSpringTestCase?

        Show
        Bruce Phillips added a comment - The test of the portlet action now passes when extending StrutsPortletTestCase. For those portlet action classes that use Spring and the Struts Spring plugin do we need a StrutsPortletSpringTestCase that extends StrutSpringTestCase?
        Hide
        Lukasz Lenart added a comment -

        Yeah, you're right ... and somehow use the same code to initialise the env

        Show
        Lukasz Lenart added a comment - Yeah, you're right ... and somehow use the same code to initialise the env
        Hide
        Michael Menzies added a comment -

        Finally got around to testing your StrutsPortletTestCase. I love the ability to override the getMajorVersion(), but unfortunately, even with this addition, this test class does not work with JSR-168. The reason being is that StateAwareResponse is part of the portlet spec 2.0 (JSR-286). So when I try to use it I get the following:

        java.lang.NoClassDefFoundError: javax/portlet/StateAwareResponse

        I don't have the portlet-api-2.0 dependency in my project. This is why I had used jmock to mock the response object in my workaround.

        As far as a solution goes... I think we create two separate test classes. StrutsPortletJSR168TestCase and StrutsPortletJSR286TestCase, which is how struts currently handles the dispatchers.

        Thoughts?

        Show
        Michael Menzies added a comment - Finally got around to testing your StrutsPortletTestCase. I love the ability to override the getMajorVersion(), but unfortunately, even with this addition, this test class does not work with JSR-168. The reason being is that StateAwareResponse is part of the portlet spec 2.0 (JSR-286). So when I try to use it I get the following: java.lang.NoClassDefFoundError: javax/portlet/StateAwareResponse I don't have the portlet-api-2.0 dependency in my project. This is why I had used jmock to mock the response object in my workaround. As far as a solution goes... I think we create two separate test classes. StrutsPortletJSR168TestCase and StrutsPortletJSR286TestCase, which is how struts currently handles the dispatchers. Thoughts?
        Hide
        Lukasz Lenart added a comment -

        Hmm... maybe you're right. But from other side it will complicate adding support for Spring ... hmm ... anyway, thanks for pointing this out!

        Show
        Lukasz Lenart added a comment - Hmm... maybe you're right. But from other side it will complicate adding support for Spring ... hmm ... anyway, thanks for pointing this out!
        Hide
        Lukasz Lenart added a comment -

        Bruce Phillips I'd like re-assign this issue to you as you have large experience in using Portlet Plugin and what is needed to test action in different Portlet environments.

        Show
        Lukasz Lenart added a comment - Bruce Phillips I'd like re-assign this issue to you as you have large experience in using Portlet Plugin and what is needed to test action in different Portlet environments.
        Hide
        Bruce Phillips added a comment -

        I'm marking this issue as resolved - I was the original reporter of the issue. Testing my portlet action I found that if I used:

        ActionProxy proxy = getActionProxy("/view/index.action");

        ListEmployeeAction action = (ListEmployeeAction) proxy.getAction() ;

        assertNotNull(action);

        String result = action.execute() ;

        assertEquals("Result of calling execute method of index action is not success but it should be.", "success", result);

        in my test method then my test is successful when my test class extends StrutsTestCase (or StrutsSpringTestCase if my portlet app is using Spring).

        The key difference is that I needed to use action.execute() (not proxy.execute).

        So the fix was to change how I wrote my test method.

        Portlets that are not using Spring can also extend StrutsPortletTestCase if they need more refined access to the mocked up PortletRequest object.

        Show
        Bruce Phillips added a comment - I'm marking this issue as resolved - I was the original reporter of the issue. Testing my portlet action I found that if I used: ActionProxy proxy = getActionProxy("/view/index.action"); ListEmployeeAction action = (ListEmployeeAction) proxy.getAction() ; assertNotNull(action); String result = action.execute() ; assertEquals("Result of calling execute method of index action is not success but it should be.", "success", result); in my test method then my test is successful when my test class extends StrutsTestCase (or StrutsSpringTestCase if my portlet app is using Spring). The key difference is that I needed to use action.execute() (not proxy.execute). So the fix was to change how I wrote my test method. Portlets that are not using Spring can also extend StrutsPortletTestCase if they need more refined access to the mocked up PortletRequest object.

          People

          • Assignee:
            Bruce Phillips
            Reporter:
            Bruce Phillips
          • Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development