How to Test Struts 2 Actions Without a Container

来源:互联网 发布:数据分析师做什么 编辑:程序博客网 时间:2024/04/28 20:50

I recently worked on a project that used Struts 2.1 and the Spring framework integration plug-in. There are sporadic examples of how to test Struts actions in this context but none of them seemed to be up-to-date or they required you to start a servlet container to run the tests. None of the examples I found also showed you how to leverage Spring's transactional JUnit helper classes which enable you to run tests in a transaction and automatically rollback after each test. This is a huge benefit if you're working with a project where lots of test data is needed.

Since none of the existing examples seemed to address my needs, I put together a solution using a combination of mock objects and Spring's test helper classes.

Here's what you need:

  • Java 5 or above
  • Struts 2.1.8.1 (including the junit and spring integration plug-ins)
  • Spring Framework 2.5.6 (including spring)
  • JUnit 4.4

The versions outlined above are the ones I used so I know they work. You may be able to use slightly different versions.

The approach I use here will have the following benefits:

  • Little to no manual setup and configuration required for tests.
  • Since the tests are transactional, there's no need to manually clean up test data. Each time the tests are run, the database returns to the state it was in before running a test.
  • Easy overrides. Using a combination of annotations and helper methods provided by JUnit, Struts and Spring superclasses, you can easily modify the default behavior of tests.

Let's get started with some code.

Extending StrutsTestCase

I started with a base class called StrutsSpringTransactionalTests which extends the StrutsSpringTestCase. The StrutsSpringTestCase is bundled with the struts2-junit-plugin.jar and provides functionality for adding the Spring context to the servlet context (normally achieved via the web descriptor).

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
...package imports omitted for brevity...
 
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
TransactionalTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class
})
@Transactional
public abstract class StrutsSpringTransactionalTests extends StrutsTestCase implements ApplicationContextAware {
 
protected final Log logger = LogFactory.getLog(getClass());
 
protected DataSource dataSource;
 
/**
* The {@link org.springframework.context.ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(org.springframework.context.ApplicationContext)}.
*/
protected ApplicationContext applicationContext;
 
 
public abstract void setDataSource(DataSource dataSource);
 
 
/**
* Set the {@link ApplicationContext} to be used by this test instance,
* provided via {@link ApplicationContextAware} semantics.
*/
public final void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
 
/**
* The SimpleJdbcTemplate that this base class manages, available to subclasses.
*/
protected SimpleJdbcTemplate simpleJdbcTemplate;
 
private String sqlScriptEncoding;
 
 
/**
* Specify the encoding for SQL scripts, if different from the platform encoding.
*
* @see #executeSqlScript
*/
public void setSqlScriptEncoding(String sqlScriptEncoding) {
this.sqlScriptEncoding = sqlScriptEncoding;
}
 
 
/**
* Count the rows in the given table.
*
* @param tableName table name to count rows in
* @return the number of rows in the table
*/
protected int countRowsInTable(String tableName) {
return SimpleJdbcTestUtils.countRowsInTable(this.simpleJdbcTemplate, tableName);
}
 
/**
* Convenience method for deleting all rows from the specified tables.
* Use with caution outside of a transaction!
*
* @param names the names of the tables from which to delete
* @return the total number of rows deleted from all specified tables
*/
protected int deleteFromTables(String... names) {
return SimpleJdbcTestUtils.deleteFromTables(this.simpleJdbcTemplate, names);
}
 
/**
* Execute the given SQL script. Use with caution outside of a transaction!
* <p>The script will normally be loaded by classpath. There should be one statement
* per line. Any semicolons will be removed. <b>Do not use this method to execute
* DDL if you expect rollback.</b>
*
* @param sqlResourcePath the Spring resource path for the SQL script
* @param continueOnError whether or not to continue without throwing an
* exception in the event of an error
* @throws org.springframework.dao.DataAccessException
* if there is an error executing a statement
* and continueOnError was <code>false</code>
*/
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError)
throws DataAccessException {
 
Resource resource = this.applicationContext.getResource(sqlResourcePath);
SimpleJdbcTestUtils.executeSqlScript(
this.simpleJdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding), continueOnError);
}
 
}
view rawStrutsSpringTransactionalTests.java hosted with ❤ by GitHub

Wiring in Spring Configuration and Setting DataSource

From here, extend the StrutsSpringTransactionalTests class:

1234567891011121314151617181920212223242526272829303132333435
 
*** package and imports omitted for brevity ***
 
@ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/appContext.xml",
"classpath:appTestContext.xml"
})
public abstract class AppTransactionalStrutsTestCase extends StrutsSpringTransactionalTests {
 
@Before
public void onSetUp() throws Exception {
super.setUp();
setupAction();
}
 
protected abstract void setupAction() throws DataAccessException;
 
@Override
protected void setupBeforeInitDispatcher() throws Exception {
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);
}
 
protected ActionProxy initActionProxy(String uri) {
ActionProxy proxy = getActionProxy(uri);
ActionContext actionContext = proxy.getInvocation().getInvocationContext();
actionContext.setSession(new HashMap<String, Object>());
return proxy;
}
 
@Autowired
public void setDataSource(@Qualifier(value = "appDataSource") DataSource appDataSource) {
this.dataSource = appDataSource;
this.simpleJdbcTemplate = new SimpleJdbcTemplate(appDataSource);
}
}
view rawAppTransactionalStrutsTestCase.java hosted with ❤ by GitHub

You can optionally combine the above two classes. I separated them in order to reuse StrutsSpringTransactionalTests across different Struts applications.

Struts Action Test Implementation

Finally, here's an example of a test class which leverages AppTransactionalStrutsTestCase:

1234567891011121314151617
public class SearchTest extends AppTransactionalStrutsTestCase {
 
private static final String BOND_NAME = "ACE 2006-ASP6 A2C";
 
@Override
protected void setupAction() throws DataAccessException {
}
 
@Test
public void testSearchByBondName() throws Exception {
 
request.addParameter("searchRequest.bondName", BOND_NAME);
 
ActionProxy proxy = initActionProxy("/trader/searchPostBack!postBack.action");
Assert.assertEquals(Action.INPUT, proxy.execute());
}
}
view rawSearchTest.java hosted with ❤ by GitHub

When the above test runs, any database operations are automagically rolled back.

原创粉丝点击