Search code examples
javaspringstruts2junit4spring-4

Struts 2.5 Junit test setup with spring integration


struts version: 2.5.25

spring version: 4.3.26

Using: struts2-rest-plugin, struts2-spring-plugin, JUnit 4

I am looking for assistance in correcting my unit test configuration which is currently throwing an exception "ServletContext must not be null". Below is the stack trace:

Unable to instantiate Action, com.hs.iws.actions.security.LoginAction,  defined for 'login' in namespace '/security'Error creating bean with name 'com.hs.iws.actions.security.LoginAction': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: ServletContext must not be null
    at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:320)
    at com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:401)
    at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:201)
    at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:76)
    at org.apache.struts2.rest.RestActionProxyFactory.createActionProxy(RestActionProxyFactory.java:50)
    at org.apache.struts2.StrutsJUnit4TestCase.getActionProxy(StrutsJUnit4TestCase.java:156)
    at com.hs.iws.actions.security.LoginActionTest.testValidation(LoginActionTest.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.hs.iws.actions.security.LoginAction': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: ServletContext must not be null
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:137)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:407)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1611)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:398)
    at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:201)
    at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:169)
    at com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:177)
    at com.opensymphony.xwork2.factory.DefaultActionFactory.buildAction(DefaultActionFactory.java:40)
    at com.opensymphony.xwork2.ObjectFactory.buildAction(ObjectFactory.java:142)
    at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:301)
    ... 43 more
Caused by: java.lang.IllegalArgumentException: ServletContext must not be null
    at org.springframework.util.Assert.notNull(Assert.java:134)
    at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:108)
    at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:98)
    at com.hs.iws.actions.IwsActionSupport.getSpringContext(IwsActionSupport.java:166)
    at com.hs.iws.actions.IwsActionSupport.setupActionSupport(IwsActionSupport.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:134)
    ... 52 more

Unit Test (spring beans are configured via annotations, we are not using any xml)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LoginActionSpringConfig.class)
public class LoginActionTest extends IwsStrutsSpringTestCase<LoginAction> {
    
    // THIS TEST RUNS OKAY
    @Test
    public void getActionMapping() {
        ActionMapping mapping = getActionMapping("/security/login");
        Assert.assertNotNull(mapping);
        Assert.assertEquals("/security", mapping.getNamespace());
        Assert.assertEquals("login", mapping.getName());
    }


    // THIS TEST PRODUCES THE ERROR WHEN CALLING getActionProxy()
    @Test
    public void testValidation() {
        
        // the login uses the create() which expects the POST method
        request.setMethod("POST");

        ActionProxy proxy = getActionProxy("/security/login");
        Assert.assertNotNull(proxy);

        LoginAction action = (LoginAction)proxy.getAction();
        Assert.assertNotNull(action);

        Assert.assertTrue("expected some field errors", !action.getFieldErrors().isEmpty());
    }
}

LoginActionSpringConfig.class

@Configuration
public class LoginActionSpringConfig {

    @Bean
    public IIwsAuthenticationService authenticationService() {
        return new IwsAuthenticationServiceTest();
    }

    @Bean
    public ISecurityLogDao securityLogDao() {
        return new SecurityLogDaoTest();
    }

    @Bean
    public IUsersDepartmentDao usersDepartmentDao() {
        return new UsersDepartmentDaoTest();
    }

    @Bean
    public IDepartmentDao departmentDao() {
        return new DepartmentDaoTest();
    }

    @Bean
    public IwsCookieInterface iwsCookie() {
        return new IwsCookieTest();
    }

}

My web application currently uses the org.springframework.web.context.ContextLoaderListener in the web.xml

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

and then specifies this contextClass

<context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

I don't know for sure, but I have a suspicion that I need to do something additional in my unit testing configuration to account for this. I have been unable to find the answer up to this point.

There is a servletContext field in StrutsJUnit4TestCase, which I have confirmed is NOT null. However, I suspect this is there to satisfy struts only and not spring. Can someone please assist in pointing me in the right direction to configure this missing ServletContext when testing my Struts actions through Junit 4?

Update 11/1 Below is the screen shot of the debugger. I was trying to say that I believe there are two ServletContext one in struts and one in spring. It looks like the struts test has populated the servletContext in the setup. Is there something missing in my configuration to satisfy the null ServletContext for spring? LoginActionTest Debug Session

Update 11/1 I have figured out where the error is coming from. The LoginAction being tested is inheriting this method which is being called in the super class.

protected WebApplicationContext getSpringContext() {
        return WebApplicationContextUtils
                .getWebApplicationContext(ServletActionContext.getServletContext());
    }

The ServletActionContext.getServletContext() is returning the null, which is the cause of the issue. How do I wire up ServletActionContext in the unit test to behave as expected?


Solution

  • I was able to resolve the issue by overriding the StrutsJunit4TestCase.getActionProxy(). My current action setup is trying to get access to the WebApplicationContext when the action is constructed, which is happening prior to the current getActionProxy() injecting the necessary objects into the ServletActionContext. Below is my override that resolved this issue

        @Override
    protected ActionProxy getActionProxy(String uri) {
    
        // the super.getActionProxy() already sets this, but does it at the end of the method.
        // the ActionProxy proxy = ... line is already expecting it to be present and produces a NPE
        ServletActionContext.setServletContext(servletContext);
        ServletActionContext.setRequest(request);
        ServletActionContext.setResponse(response);
    
        return super.getActionProxy(uri);
    }