Nov
6
Enabling TDD by Integrating Spring and StrutsTestCase
Posted by Keith McMillan
November 6, 2008 | 5 Comments
I’m currently mentoring a team at my client’s site that’s using test driven development (TDD) to improve the design of their application. We’re also using Struts 1.x, and they want to use StrutsTestCase as a standard for testing. Okay, so far so good.
Being a fan of the Spring Framework, it should be no surprise that I want to make dependency injection a big part of refactoring to make their application more modular. So I set off, Google in hand, to try to figure out how to make Struts, StrutsTestCase and Spring all play nice together. Turns out that it can be done, but it’s surprisingly difficult, and I couldn’t find anything telling me exactly how to do it. There were hints in a number of places, but it took me a couple of days to put all of those together. I’m going to share, so hopefully you won’t have to go through what I did.
The Background
The Spring Framework (I’m just going to call it “Spring”) is a popular dependency injection and aspect oriented programming (AOP) tool. It allows us to externalize the configuration of our application into XML files, removing any sort of factory references from the code itself. There are lots of good tutorials out there, so I’m going to assume you know how plain-old vanilla Spring works, and leave off all the basics. From here on in, it’s specifically how do you get Spring to work with Struts, and then with StrutsTestCase.
Spring has built support for Struts, and for the version we’re talking about (Struts 1.x), that consists primarily of a couple of things: context loaders and a way to look up beans in those contexts. We’ll start by talking about the lookup mechanism.
Looking up your beans
Spring provides a number of ways to hook into Struts. The option we chose was to use Spring’s DelegatingActionProxy. As an aside, there are two other options. You can use the DelegatingRequestProcessor, registering it with the Struts ActionServlet, and the processor would then look up the beans for the ActionServlet. Alternatively, you can subclass ActionSupport in your actions, which just gives you support to look up beans yourself in your actions.
When you use the DelegatingActionProxy, you set it as the type for the Struts Action. Here’s an example from a struts-config.xml:
…
<action-mappings>
<action path=”/login” name=”loginForm” scope=”session”
type = “org.springframework.web.struts.DelegatingActionProxy”>
<forward name=”success” path=”/jsp/welcome.jsp”/>
<forward name=”failure” path=”/jsp/login.jsp”/>
</action>
…
The key part of this is the type attribute for the action, which is pointed at that Spring-provided DelegatingActionProxy. This proxy is going to look for a spring bean with the same name as the action, so we need to register a Spring bean. We do this in a normal Spring application context XML file. In this example, that means it has to be called “/login”:
…
<bean name=”/login” class=”com.adeptechllc.myapp.view.action.LoginAction”/>
…
Loading the Application Contexts
In order to look up a bean in a spring context, you have to load that context. Here, Spring provides us with two different loaders. The first is a standard web application listener called ContextLoaderListener. This class loads the Spring WebApplicationContext, reading it from the applicationContext.xml file by default.
The second loader is the ContextLoaderPlugIn, and it’s provided as part of Spring’s Struts support. It reads a file whose name is based on the name of the action servlet. Assuming you are calling your action servlet “action,” it would look for a file called “action-servlet.xml”.
The context that’s loaded by the plug in automatically refers to the one loaded by the listener as it’s parent. This means that if a bean isn’t found in the plug-in’s file, we look for it in the listener’s file. Here’s a graphical representation of that, because a picture helps:
Spring intends that you put your Struts Actions into the action-servlet.xml file, and the dependencies (the business objects) into the applicationContext.xml file, but if you read the reference manual, you get this curious bit of throwaway in reference to using the plug in to load both the application context and the struts context:
It is possible to use this plugin to load all your context files, which can be useful when using testing tools like StrutsTestCase. StrutsTestCase’s MockStrutsTestCase won’t initialize Listeners on startup so putting all your context files in the plugin is a workaround. (A bug has been filed for this issue, but has been closed as ‘Wont Fix’).
And so we learn that StrutsTestCase doesn’t run the listeners, but does run the plugin. That means that if we follow the stanard structure, our Struts Action beans would be registered, but their dependencies would not be.
Contrary to the advice in the reference manual, we’re going to leave our Struts Action beans in the action-servlet.xml, leave their dependencies in applicationContext.xml, and not direct the plug in to load the main applicationContext.xml. We’re going to load a testing version of the applicationContext in our test cases instead.
Getting Your Mock Objects Into The Web Application Context
So far, we’ve seen how to set up Struts to look up our beans using the DelegatingActionProxy, and how to register our beans via the ContextLoaderPlugIn and the ContextLoaderListener. This works fine for normal application wiring, but what about when we want to test the actions in isolation, without all their dependencies? To do this, we need to inject mock versions of those dependencies.
Some posts I found on the web simply punt at this point: they suggest using a factory to look up your dependencies. This didn’t sit well with me. Since I’m already using Spring, why should I have another factory? I should be able to have Spring do it for me, and my code will be cleaner too.
It would seem like all you’d need to do was to tell the listener to load a different file when you’re testing. The listener, however, reads only an initialization parameter to retrieve the location and name of the file. Then, we’d just have to poke the listener to load that testing file (remember, we need to poke the loader since StrutsTestCase doesn’t run listeners).
Sounds like a great idea, but unfortunately, the standard Java servlet context doesn’t give us access to add initialization parameters, so we’re out of luck. You might think that StrutsTestCase would give us this ability, since it simulates the container, but it also doesn’t give us access to this functionality. My client is using open source, but they don’t want to modify it (ah bureaucracy at it’s best), so I’ve got to figure out a way to make it work as-is.
The nugget of code that allows us to do this comes courtesy of a posting by Matt Raible, in which he linked to a base class that loads the root WebApplicationContext for a MockStrutsTestCase. I dug for two days to find that posting and the link, and there wasn’t much in the way of explanation, so I’ll explain it to you.
The trick is to use the Spring MockServletContext, because it gives us access to add an initialization parameter, and use it as a sandbox to build the WebApplicationContext. Then we transfer the context to the StrutsTestCase ServletContext, where the DelegatingActionProxy can use it.
As part of the setUp for your test case, you need to use something like the following incantation:
public class MyStrutsTestCase extends MockStrutsTestCase { protected void setUp() throws Exception { super.setUp(); // Create a sandbox in which we can create the WebApplicationContext String pkg = ClassUtils.classPackageAsResourcePath(this.getClass()); MockServletContext sandbox = new MockServletContext(""); sandbox.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "classpath*:/" + pkg + "/testApplicationContext.xml"); // Create a context loader and load the WebApplicationContext into the sandbox ServletContextListener contextListener = new ContextLoaderListener(); ServletContextEvent event = new ServletContextEvent(sandbox); contextListener.contextInitialized(event); // Fetch the WebApplicationContext from the sandbox and move it to the StrutsTestCase ServletContext getSession().getServletContext().setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, sandbox.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
First, we create MockServletContext, and set the parameter that tells the ContextLoaderListener where to find it’s data. In this case, it’s a generic “testApplicationContext.xml” in the same directory as the test, but you have a bunch of options here. Next we have a ContextLoaderListener set up a WebApplicationContext in that MockServletContext. Finally, we take the WebApplicationContext and copy it into the ServletContext that the test case is expecting to use.
Now, we can inject whatever mock objects we need in order to test our StrutsActions using StrutsTestCase.
Comments
5 Comments »
Blogroll
- Ars Technica
- Dark Reading - IT Security
- Help Net Security
- InformIT
- SANS Internet Storm Center
- Schneier on Security - Dr. Bruce Schieier’s blog
- Security Info Watch
- What to Fix - Daniel Markham, fellow consultant
- Wired Gadget Lab
- Wordpress Documentation
- WordPress Planet
- Wordpress Support Forum
Thanks a lot for the sanbox tips, very usefull
I’m happy that you found it useful!
Very good post. Really what i need.
Clear, concise and good explained.
Thx
[…] approach to this problem, which is more suited for integration tests is well explained by Keith McMillan. Share this:Like this:LikeBe the first to like this post. This entry was posted in Uncategorized […]
Thanks a lot. I was in the same context and that was exactly what I needed. Without your help I would probably never have achieved this!
Just one piece was missing for me – how to retrieve the mocks from the Spring application context to setup my test case.
Here’s what I did (using mockito):
– in testApplicationContext.xml:
<bean id="myService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.myproject.MyService" />
</bean>
– in the test class:
XmlWebApplicationContext applicationContext = (XmlWebApplicationContext) sandbox.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
MyService myService = (MyService) applicationContext.getBean("myService");
when(myService.doSomething()).thenReturn(something);
Just wondering if this is the right way to do?