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?
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?
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);
}