Testing is an important part of any application, and testing a Struts application is easier than most. The generic command pattern provided by XWork doesn't depend on the Servlet API at all. This makes it easy to use JUnit to test your Actions.
Create a ProjectActionTest.java class in src/test/java/**/webapp/action.
package com.ibm.ics.webapp.action;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.apache.struts2.ServletActionContext;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import com.ibm.ics.model.Project;
import com.ibm.ics.service.GenericManager;
import com.opensymphony.xwork2.ActionSupport;
public class ProjectActionTest extends BaseActionTestCase {
private ProjectAction action;
@Before
public void onSetUp() {
super.onSetUp();
action = new ProjectAction();
GenericManager projectManager = (GenericManager) applicationContext
.getBean("projectManager");
action.setProjectManager(projectManager);
// add a test project to the database
Project project = new Project();
// enter all required fields
projectManager.save(project);
}
@Test
public void testGetAllProjects() throws Exception {
assertEquals(action.list(), ActionSupport.SUCCESS);
assertTrue(action.getProjects().size() >= 1);
}
@Test
public void testEdit() throws Exception {
log.debug("testing edit...");
action.setId(1L);
assertNull(action.getProject());
assertEquals("success", action.edit());
assertNotNull(action.getProject());
assertFalse(action.hasActionErrors());
}
@Test
public void testSave() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletActionContext.setRequest(request);
action.setId(1L);
assertEquals("success", action.edit());
assertNotNull(action.getProject());
// update last name and save
action.getProject().setName("Updated Last Name");
assertEquals("input", action.save());
assertEquals("Updated Last Name", action.getProject().getName());
assertFalse(action.hasActionErrors());
assertFalse(action.hasFieldErrors());
assertNotNull(request.getSession().getAttribute("messages"));
}
@Test
public void testRemove() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletActionContext.setRequest(request);
action.setDelete("");
Project project = new Project();
project.setId(2L);
action.setProject(project);
assertEquals("success", action.delete());
assertNotNull(request.getSession().getAttribute("messages"));
}
}
This class won't compile yet; you must first create the ProjectAction class.
Create a ProjectAction.java class (that extends AppFuse's BaseAction) in src/main/java/**/webapp/action:
package com.ibm.ics.webapp.action;
import java.util.List;
import com.ibm.ics.model.Project;
import com.ibm.ics.service.GenericManager;
public class ProjectAction extends BaseAction {
private GenericManager<Project, Long> projectManager;
private List<Project> projects;
public void setProjectManager(GenericManager<Project, Long> projectManager) {
this.projectManager = projectManager;
}
public List<Project> getProjects() {
return projects;
}
public String list() {
projects = projectManager.getAll();
return SUCCESS;
}
private Project project;
private Long id;
public void setId(Long id) {
this.id = id;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public String delete() {
projectManager.remove(project.getId());
saveMessage(getText("project.deleted"));
return SUCCESS;
}
public String edit() {
if (id != null) {
project = projectManager.get(id);
} else {
project = new Project();
}
return SUCCESS;
}
public String save() throws Exception {
if (cancel != null) {
return CANCEL;
}
if (delete != null) {
return delete();
}
boolean isNew = (project.getId() == null);
project = projectManager.save(project);
String key = (isNew) ? "project.added" : "project.updated";
saveMessage(getText(key));
if (!isNew) {
return INPUT;
} else {
return SUCCESS;
}
}
}
If you look at your ProjectActionTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (src/test/resources/sample-data.xml). Adding it at the bottom should work - order is not important since it (currently) does not relate to any other tables. If you already have this table, make sure the 2nd record exists so testRemove() doesn't fail.
<table name='project'>
<column>id</column>
<column>name</column>
<column>description</column>
<row>
<value>1</value>
<value>connections</value>
<value>this is connections project description.</value>
</row>
<row>
<value>2</value>
<value>sametime</value>
<value>this is sametime project description.</value>
</row>
</table>
Run the ProjectActionTest using your IDE or mvn test -Dtest=ProjectActionTest.
Create a src/main/webapp/WEB-INF/pages/projectList.jsp page to display the list of project:
<%@ include file="/common/taglibs.jsp"%>
<head>
<title><fmt:message key="projectList.title"/></title>
<meta name="heading" content="<fmt:message key='projectList.heading'/>"/>
<meta name="menu" content="ProjectMenu"/>
</head>
<input type="button" style="margin-right: 5px" class="button" onclick="location.href='<c:url value="/editProject"/>'" value="<fmt:message key="button.add"/>"/>
<input type="button" class="button" onclick="location.href='<c:url value="/mainMenu"/>'" value="<fmt:message key="button.done"/>"/>
<display:table name="projects" class="table" requestURI="" id="projectList" export="true" pagesize="25">
<display:column property="id" sortable="true" href="editProject" media="html"
paramId="id" paramProperty="id" titleKey="project.id"/>
<display:column property="id" media="csv excel xml pdf" titleKey="project.id"/>
<display:column property="name" sortable="true" titleKey="project.name"/>
<display:column property="description" sortable="true" titleKey="project.description"/>
<display:setProperty name="paging.banner.item_name"><fmt:message key="projectList.project"/></display:setProperty>
<display:setProperty name="paging.banner.items_name"><fmt:message key="projectList.projects"/></display:setProperty>
<display:setProperty name="export.excel.filename"><fmt:message key="projectList.title"/>.xls</display:setProperty>
<display:setProperty name="export.csv.filename"><fmt:message key="projectList.title"/>.csv</display:setProperty>
<display:setProperty name="export.pdf.filename"><fmt:message key="projectList.title"/>.pdf</display:setProperty>
</display:table>
<input type="button" style="margin-right: 5px" class="button" onclick="location.href='<c:url value="/editProject"/>'" value="<fmt:message key="button.add"/>"/>
<input type="button" class="button" onclick="location.href='<c:url value="/mainMenu"/>'" value="<fmt:message key="button.done"/>"/>
<script type="text/javascript">
highlightTableRows("projectList");
</script>
Create a src/main/webapp/WEB-INF/pages/projectForm.jsp page to display the form:
<%@ include file="/common/taglibs.jsp"%>
<head>
<title><fmt:message key="projectDetail.title"/></title>
<meta name="heading" content="<fmt:message key='projectDetail.heading'/>"/>
</head>
<s:form id="projectForm" action="saveProject" method="post" validate="true">
<li style="display: none">
<s:hidden key="project.id"/>
</li>
<s:textfield key="project.name" required="false" cssClass="text medium"/>
<s:textfield key="project.description" required="false" cssClass="text medium"/>
<li class="buttonBar bottom">
<s:submit cssClass="button" method="save" key="button.save" theme="simple"/>
<c:if test="${not empty project.id}">
<s:submit cssClass="button" method="delete" key="button.delete"
onclick="return confirmDelete('Project')" theme="simple"/>
</c:if>
<s:submit cssClass="button" method="cancel" key="button.cancel" theme="simple"/>
</li>
</s:form>
<script type="text/javascript">
Form.focusFirstElement($("projectForm"));
</script>
Next, update the src/main/resources/struts.xml file to include the "projects", "editProject" and "saveProject" actions.
<action name="projects" class="com.ibm.ics.webapp.action.ProjectAction"
method="list">
<result>/WEB-INF/pages/projectList.jsp</result>
</action>
<action name="editProject" class="com.ibm.ics.webapp.action.ProjectAction"
method="edit">
<result>/WEB-INF/pages/projectForm.jsp</result>
<result name="error">/WEB-INF/pages/projectList.jsp</result>
</action>
<action name="saveProject" class="com.ibm.ics.webapp.action.ProjectAction"
method="save">
<result name="input">/WEB-INF/pages/projectForm.jsp</result>
<result name="cancel" type="redirectAction">projects</result>
<result name="delete" type="redirectAction">projects</result>
<result name="success" type="redirectAction">projects</result>
</action>
Open src/main/resources/ApplicationResources.properties and add i18n keys/values for the various "project" properties:
# -- project form --
project.id=Id
project.firstName=First Name
project.lastName=Last Name
project.added=Project has been added successfully.
project.updated=Project has been updated successfully.
project.deleted=Project has been deleted successfully.
# -- project list page --
projectList.title=Project List
projectList.heading=Projects
projectList.project=Project
projectList.projects=Projects
# -- project detail page --
projectDetail.title=Project Detail
projectDetail.heading=Project Information
Struts 2 allows two types of validation: per-action and model-based. Since you likely want the same rules applied for the project object across different actions, this tutorial will use model-based.
Create a new file named Project-validation.xml in the src/main/resources/**/model directory (you'll need to create this directory). It should contain the following XML:
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="project.name">
<field-validator type="requiredstring">
<message key="errors.required" />
</field-validator>
</field>
</validators>
Now you need to configure ProjectAction to know about visitor validation. To do this, create a ProjectAction-validation.xml file in src/main/resources/**/webapp/action (you'll need to create this directory). Fill it with the following XML:
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="project">
<field-validator type="visitor">
<param name="appendPrefix">false</param>
<message/>
</field-validator>
</field>
</validators>
Where menu.viewProject is an entry in src/main/resources/ApplicationResources.properties.
menu.viewProject=View Project
The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to src/main/webapp/WEB-INF/menu-config.xml:
<Menu name="ProjectMenu" title="menu.viewProject" page="/projects"/>
Make sure the above XML is inside the <menus> tag, but not within another </menus>