Unit Testing Struts 2.0 (Part 3)
Like Dan commented on the Unit Testing Struts 2.0 (Part 2) post, Struts 2.0 has changed it's API enough to make my previous code not work on latest version. So, in response to his comment, here is what I am using right now. Credit where it's due, the setup code is the one that The Arsenalist pointed out on his blog post Unit Testing Struts 2 Actions wired with Spring using JUnit. Again, credit where it's due...my thanks go to "The Arsenalist" for posting his solution.
/**
* Class for easier support of Struts related
* testing. Takes care of all the configuration details
* that allow test classes to create beans (Spring),
* actions (Struts), intercepted actions (Struts).
* Class is singleton to minimize hit of initializing
* Struts and related infrastructure (e.g. Hibernate).
*
* Adapted from code from "The Arsenalist" (http://arsenalist.com/),
* see http://arsenalist.com/2007/06/18/unit-testing-struts-2-actions-spring-junit/
*/
public class StrutsTestCaseSupport {
/**
* Singleton variable
*/
public static StrutsTestCaseSupport _theInstance = null;
/**
* Servlet context
*/
private ServletContext servletContext = null;
/**
* Request dispatcher
*/
private Dispatcher dispatcher = null;
/**
* Singleton access
*/
public static synchronized StrutsTestCaseSupport getInstance()
throws Exception {
if ( _theInstance == null ) {
_theInstance = new StrutsTestCaseSupport();
}
return _theInstance;
}
/**
* Class constructor, take care of Struts initializations
*/
private StrutsTestCaseSupport ()
throws Exception {
String[] config = new String[] { "/WEB-INF/applicationContext.xml" };
// Link the servlet context and the Spring context
servletContext = new MockServletContext(new FileSystemResourceLoader());
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setServletContext(servletContext);
appContext.setConfigLocations(config);
appContext.refresh();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, appContext);
// Use spring as the object factory for Struts
StrutsSpringObjectFactory ssf = new StrutsSpringObjectFactory(null, null, servletContext);
ssf.setApplicationContext(appContext);
StrutsSpringObjectFactory.setObjectFactory(ssf);
// Dispatcher is the guy that actually handles all requests. Pass in
// an empty Map as the parameters but if you want to change stuff like
// what config files to read, you need to specify them here
// (see Dispatcher's source code)
dispatcher = new Dispatcher(servletContext, new HashMap());
dispatcher.init();
Dispatcher.setInstance(dispatcher);
}
/**
* create a bean from the object factory (all wired up from Spring)
*
* @param beanName the name of the bean to get from the object factory
* @param extraContent any extra content information to pass along to the bean building
* process
* @return the object factory created bean
* @throws Exception on processing, configuration errors, test failure
*/
public Object createBean ( String beanName, Map
Like mentioned in previous postings, using this class is pretty straightforward, within your test you just do for beans:
YourClass yourInstance = (YourClass)StrutsTestCaseSupport.getInstance().createBean("yourBeanId",new HashMap());
For Actions:
ActionProxy proxy = StrutsTestCaseSupport.getInstance().createActionProxy(yourActionId,yourContextPath);
MyActionClass myActionInstance = (MyActionClass)StrutsTestCaseSupport.getInstance().createAction(yourActionId,yourContextPath);
This code is currently being used with Struts 2.0.8.
19 comments:
Thanks for your post.
But I still cannot run test.
I have two questions, could you please answer for me? Thanks in advance.
1. My test class should extend "TestCase" or it's just a normal java program?
2. I created my test class extends TestCase ( JUnit) and it said that could not find : /WEB-INF/applicationContext.xml
Thank you very much!
Hi Bui,
Answers to your questions:
1. The Struts test support is oblivious of your testing infrastructure. If you're using JUnit you will have to follow JUnit's test building procedures (derive from TestCase), I actually use TestNG and am not required to derive from any other class, my test class is a regular POJO.
2. Fair enough...this assumes two things: a) /WEB-INF/applicationContext.xml is your Spring bean definition file. You might need to change to reflect your bean definition file. b) You can find WEB-INF/applicationContext.xml either in your classpath or under your test run working directory.
I hope this helps.
The getResponseContentAsString() always returns an empty string for me. I've tried to verify that the MockServletContext with the correct resourceBasePath and a FileSystemResourceLoader, but I'm convinced that it doesn't actually do anything to compile and run jsps. Is there something I'm missing?
Thanks for the very useful example. I'm very grateful. Here's a fairly minor bug in the example:
createActionProxy ( String actionName, String actionNamespace, Map sessionMap)
This calls:
createActionProxy ( String actionName, String actionNamespace )
However, when it calls the 2 parameter version, the parameters are reversed:
// create an action proxy as usual
ActionProxy actionProxy = createActionProxy(actionName, actionNamespace);
Thanks again.
I accidentally copied and pasted my 'fixed' version. But the point is still valid :)
Thanks Hayden! Corrected the example...great catch! :-)
I am using maven and all my config files are under my resource directory. How should I list them?
I am getting the following error when attempting to JUnit test my actions. So what am I doing wrong in how I use StrutsTestCaseSupport? -
There is no Action mapped for namespace /login.html and action name loginAction. - [unknown location]
at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:186)
at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:41)
at com.mycompany.sales.action.StrutsTestCaseSupport.createActionProxy(StrutsTestCaseSupport.java:132)
at com.mycompany.sales.action.StrutsTestCaseSupport.createAction(StrutsTestCaseSupport.java:186)
at com.mycompany.core.action.LoginActionTest.testList(LoginActionTest.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
...
Here is the example spring bean config -
applicationContext.xml -
bean id="loginAction" scope="prototype"
class="com.mycompany.core.action.LoginAction">
property name="userService" ref="userService"
Here is the example Struts.xml -
constant name="struts.devMode" value="false"
constant name="struts.i18n.encoding" value="UTF-8"
constant name="struts.action.extension" value="html"
constant name="struts.objectFactory" value="spring"
constant name="struts.custom.i18n.resources" value="ApplicationResources,errors"/>
constant name="struts.multipart.maxSize" value="2097152"
constant name="struts.enable.SlashesInActionNames" value="true"
package name="sales" extends="struts-default"
action name="login" class="com.mycompany.core.action.LoginAction">
result /login.jsp result
/action
...
LoginActionTest.java -
public class LoginActionTest extends TestCase {
...
LoginAction action = (LoginAction)StrutsTestCaseSupport.getInstance().
createAction("loginAction","/login.html");
Hi Kent,
Try the following instead:
LoginAction action = (LoginAction) StrutsTestCaseSupport.getInstance().createAction("login","/");
Remember that 'createAction' first parameter is the action name, second parameter is the namespace for the action.
The trick here is that your struts.xml file defined the 'login' action within the 'sales' package. 'loginAction' is really not an action you can find defined in your struts.xml (the 'action name=...' part), and '/login.html' would not be a valid namespace (according to your struts.xml)...
Hope that helps...
Hello,
I found your article extremely useful. I joined a project recently which has been using Struts 1.1 and wanted to find out if its possible to use Spring's unit testing functionality with Struts 1.1.
Thanks for your help.
Been helped too many times to count by people on the web posting experiences, problems, solutions. Glad to be able to pay it back by helping someone back. :-)
Thanks. This example worked for me and I can test my beans.
(After changing "/WEB-INF/applicationContext.xml" to "classpath*:/WEB-INF/applicationContext.xml")
But I have an EntityManager in my bean, who is injected by @PersistenceContext.
And this injection doesn't work, when I run the JUnit tests.
Does anyone have an idea, why the Annotation doesn't work?
I really want to get this running but am having a lot of trouble. Here are the errors when I try to build it:
org.apache.struts2.config.Settings is not public in org.apache.struts2.config; cannot be accessed from outside package
[javac] import org.apache.struts2.config.Settings;
package org.springframework.mock.web does not exist
[javac] import org.springframework.mock.web.MockServletContext;
package org.springframework.mock.web does not exist
[javac] import org.springframework.mock.web.MockHttpServletResponse;
[javac] symbol : class MockServletContext
[javac] location: class org.apache.struts2.StrutsTestCaseSupport
[javac] servletContext = new MockServletContext(new FileSystemResourceLoader());
There are a bunch more but I think they are all related to the above errors.
Is it my version of struts? Here are some of the struts libraries I have:
strutstest-2.1.4.jar
struts2-core-2.0.11.jar
Thanks for any help.
So I figured out the first few errors for the missing org.springframework.mock.web stuff.
But I am still having issues with this:
StrutsTestCaseSupport.java:11: org.apache.struts2.config.Settings is not public in org.apache.struts2.config; cannot be accessed from outside package
[javac] import org.apache.struts2.config.Settings;
Do I need something else? A special library?
Thanks.
Hi Francisco Assis Rosa
Thanks a lot for the post.
I have issue with bean scope.
I am using struts SessionAware classes and I have to do the offline testing of my webapplication.
When I do the testing by using this example. The testing halts due to the no session or request thread bound as error suggested.
Please suggest solution for the same, what could be fault in the code.
---------WEB.xml----------------
**context-param**
*parama* pplicationContext.xml *param*
**context-param**
**no filters**
**listener**
*listener-class*
org.springframework.web.context.request.RequestContextListener
*listener-class*
**listener**
----------------End Web.xml -------
---------------applicationContext.xml---------
Contains all DAO bean
------end applicationContext.xml---
------code------------------------
public static void main(String args[]) throws Throwable {
try {
String executionPath = System.getProperty("user.dir");
System.out.println(executionPath);
System.out.print("Executing at =>"+executionPath.replace("\\", "/"));
System.out.println(getClassLocation());
LoginAction loginAction = (LoginAction) StrutsTestCaseSupport.getInstance()
.createAction("Login", "/");
String results = loginAction.execute();
System.out.println("Result :" + results);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
--------------end code---------
---------error ------------------
Unable to instantiate Action, jp.co.nec.nhm.sm.web.action.LoginAction, defined for 'Login' in namespace '/'No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. - action - file:/E:/eclipse/eclipseGN/workspace/1117SystemManager/target/classes/struts.xml:151:72
at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:294)
at com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:365)
at com.opensymphony.xwork2.DefaultActionInvocation.access$000(DefaultActionInvocation.java:38)
at com.opensymphony.xwork2.DefaultActionInvocation$1.doProfiling(DefaultActionInvocation.java:83)
at com.opensymphony.xwork2.util.profiling.UtilTimerStack.profile(UtilTimerStack.java:455)
at com.opensymphony.xwork2.DefaultActionInvocation.*init*(DefaultActionInvocation.java:74)
at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:189)
at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:41)
at test.StrutsTestCaseSupport.createActionProxy(StrutsTestCaseSupport.java:122)
at test.StrutsTestCaseSupport.createAction(StrutsTestCaseSupport.java:183)
at test.SpringTest.main(SpringTest.java:22)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:121)
at org.springframework.web.context.support.WebApplicationContextUtils$1.getObject(WebApplicationContextUtils.java:113)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:660)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:611)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1039)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:950)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:325)
at com.opensymphony.xwork2.spring.SpringObjectFactory.autoWireBean(SpringObjectFactory.java:167)
at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:154)
at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:128)
at com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:143)
at com.opensymphony.xwork2.ObjectFactory.buildAction(ObjectFactory.java:113)
at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:275)
... 10 more
-------end error----------------
Hi
I am trying to use your code with struts spring plugin 2.1.8.jar and struts2 2.1.6.jar but I am having problems with StrutsSpringObjectFactory
I does not compile, the
StrutsSpringObjectFactory objectFactory = new StrutsSpringObjectFactory();
Other compilation errors too. Please can you help how to resolve?
Post a Comment