Unit testing has long been considered a critical part of the development cycle for Java developers, especially those practicing test-driven development or Agile methodologies. This should be no different when using a portlet framework such as Spring Portlet MVC.  This post will discuss a few examples of writing unit tests for testing various Spring Portlet MVC components.

Context Loader Mocking

Consider a portlet with one or more MVC Controller objects. Using Spring MVC removes your dependency on a servlet container. You should be able to test your controllers, and your entire web stack, from a testing harness like JUnit. However, by using Spring you become dependent on services provided by DispatcherServlet and a complete WebApplicationContext which includes request parameter binding, validation, model attributes, request mappings, and aspects such as Spring Security. The initial step to unit testing with Spring MVC is to create a mock servlet context and WebApplicationContext from within JUnit. Create a custom mock ContextLoader to set up the Portlet environment. We create MockPortletContext and MockPortletConfig objects using our MockWebApplication for configuration. Then we create an instance of DispatcherPortlet and configure it. Notice the use of a MockViewResolver:

final ViewResolver viewResolver = new MockViewResolver();
portletApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcesor() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerResolvableDependency(DispatcherPortlet.class, dispatcherPortlet);
beanFactory.registerResolvableDependency(ViewResolver.class, viewResolver);
}
});

This ViewResolver implementation stores a view name and model, so we can check them later on for testing purposes.  This means we can ignore whatever view technology we are using for the tests.  The ContextLoader will be given the opportunity to load an application context and create a servlet environment for the tests before they are run.

Controller Testing

Consider a simple controller example derived from a recent client portlet project I worked on.

@Controller(“userAccountController”)
@RequestMapping(“VIEW”)
@SessionAttributes(“account”)
public class UserAccountController {
private UserAccountService userAccountService;
@Autowired
public UserAccountController(@Qualifier(“userAccountService”) UserAccountService userAccountService) {
this.userAccountService = userAccountService;
}
}

along with its autowired service

@Service(value=”userAccountService”)
public class UserAccountServiceImpl extends ApplicationObjectSupport implements UserAccountService {
...
}

To test this controller I created a JUnit class and injected the DispatcherPortlet and ViewResolver from the application context. Note the use of the test application context in the configuration.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:applicationContext-test.xml”},
loader=MockWebApplicationContextLoader.class)
public class UserAccountsControllerIntegrationTest {
@Autowired
private DispatcherPortlet dispatcherPortlet;
@Autowired
private MockViewResolver viewResolver;
@Autowired
@Qualifier(“userAccountService”)
private UserAccountService userAccountService;
}

Since the UserAccountsController has listed @SessionAttributes, we need to add a UserAccount attribute to the portlet session for each request:

private void setupRequestSession(PortletRequest request) {
UserAccount userAccount = new UserAccount(username,accountNumber, accountType);
userAccount.setKey(KEY);
PortletUtils.setSessionAttribute(request, “userAccount”, userAccount);
}

This then allows us to set some expected parameters, call dispatcherPortlet.render(request, response) or dispatcherPortlet.processAction(request, response), and ensure the correct action or render method gets called. Testing Render Methods In this example, there are four render methods in the controller under test; setting no extra parameters in the render request should call the viewListOfUserAccounts method. We can test this as follows:

@Test
public void viewListOfUserAccounts() throws ServletException, IOException, PortletException {
MockRenderRequest request = new MockRenderRequest();
setupRequestSession(request);
dispatcherPortlet.render(request, new MockRenderResponse());
}

If we want to trigger a different render method, some parameters will need adding to the request.  We can use addParameter() on MockRenderRequest:

@Test
public void viewUserAccount() throws ServletException, IOException, PortletException {
final Integer TEST_ID = 2;
MockRenderRequest request = new MockRenderRequest();
setupRequestSession(request);
request.addParameter(“userAccount”, TEST_ID.toString());
request.addParameter(“action”, “viewUserAccount”);
dispatcherPortlet.render(request, new MockRenderResponse());
}

With render methods, the first thing to check is that we get the expected view name returned.The DispatcherPortlet itself will usually handle this, but for test purposes we can utilize our MockViewResolver:

assertEquals(“Unexpected view name found”, “userAccounts”, viewResolver.getViewName());

We may also want to check what gets added to the Spring Model; this will be stored as a request attribute under ViewRendererServlet.MODEL_ATTRIBUTE, so we can check that expected values have been added:

assertNotNull(“No UI model found”, request.getAttribute(ViewRendererServlet.MODEL_ATTRIBUTE));
ModelMap model = (ModelMap) request.getAttribute(ViewRendererServlet.MODEL_ATTRIBUTE);
assertNotNull(“Null list of userAccounts found in model”, model.get(“userAccounts”));
assertTrue(“List of userAccounts found in model was not a Sorted Set”, model.get(“userAccounts”) instanceof SortedSet);
SortedSet modelUserAccounts = (SortedSet) model.get(“userAccounts”);
assertEquals(“Incorrect number of userAccounts found in model”,
userAccountService.getAllUserAccounts().size(), modelUserAccounts.size());

Testing Action Methods

Action methods generally require more request parameters to be added. For instance, the deleteUserAccount method

@RequestMapping(params = “action=deleteUserAccount”)
public void deleteUserAccount(ActionResponse response, @RequestParam(“userAccount”) Integer id) {
userAccountService.deleteUserAccount(id);
}

will need request parameters

request.addParameter(“action”, “deleteUserAccount”);
request.addParameter(“userAccount”, “2″);

There can also be action methods with formal parameters annotated as @ModelAttribute, such as submitAddUserAccountFormPage:

@RequestMapping(params = “action=addUserAccount”)
public void submitAddUserAccountFormPage(ActionRequest request, ActionResponse response, @ModelAttribute(“userAccount”) UserAccount userAccount,
BindingResult result, @RequestParam(“_page”) int currentPage, Model model) {
...
}

In this case we’ve already covered the “userAccount” parameter, adding it as a portlet session attribute – so testing this method we just need to add

request.addParameter(“action”, “addUserAccount”);
request.addParameter(“_page”, “1″);

These tests generally check the existing state from the injected UserAccountService, call the processAction() method of the dispatcherPortlet, then re-check the UserAccountService to make sure the correct update has occurred.

Testing  Action to Render Method Flow

We can run an action, take the render parameters set by the action, and check that the expected render method is then called. Using the editUserAccount as an example, we can set-up the action request as per the previous test, then use the response from processAction() to set render parameters:

renderRequest.setParameters(actionResponse.getRenderParameterMap());

We can then call render(), and check both the returned view, and the userAccount service to ensure the action has completed as expected.

Testing Other Components

For mocking of service layers or external resources (such as database or web service layers) outside of a container, consider using a mocking framework such as EasyMock or Mockito.

Validation Testing

Using Spring Portlet MVC, we often have controller that processes user input from forms.  For such logic, we have validation of field data in the Model.  In my blog post on Spring MVC Validation, I discussed various ways to use validation.   Here we will discuss testing such validations.
In the controller, I have a validator wired in:

@Controller(“userAccountController”)
@RequestMapping(“VIEW”)
@SessionAttributes(“account”)
public class UserAccountController {
private UserAccountService userAccountService;
@Autowired
@Qualifier("myValidator")
private Validator myValidator;
....

The Validator class looks like this

public class AddUtilityAccountValidator implements Validator {
private Validator validator;
public void setValidator(Validator validator) {
this.validator = validator;
}
@Override
public boolean supports(Class<!--?--> clazz) {
return clazz.equals(CreateUtilityAccountForm.class);
}
@Override
public void validate(Object target, Errors errors) {
validator.validate(target, errors);
CreateUtilityAccountForm form = (CreateUtilityAccountForm)target;
if (StringUtils.isBlank(form.getAccountNumber())) {
errors.rejectValue("accountNumber","NotEmpty");
}else if (StringUtils.isNotBlank(form.getAccountNumber()) &&
!StringUtils.isNumeric(form.getAccountNumber())) {
errors.rejectValue("accountNumber", "NotNumeric");
}
if (StringUtils.isNotBlank(form.getSsnLast4()) &&
!StringUtils.isNumeric(form.getSsnLast4())) {
errors.rejectValue("ssnLast4", "NotNumeric");
}
if (StringUtils.isNotBlank(form.getZipCode()) &&
!StringUtils.isNumeric(form.getZipCode())) {
errors.rejectValue("zipCode", "NotNumeric");
}
if (StringUtils.isBlank(form.getDescription())) {
errors.rejectValue("description", "NotEmpty");
} else if (!StringUtils.isAlphanumericSpace(form.getDescription())) {
errors.rejectValue("description", "NotAlphaNumericSpace");
}
}
...

For this example, a test class would be as follows below. The first two tests cover all valid and all invalid, respectively, the third covers one attribute invalid.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-test.xml")
@TestExecutionListeners(value = { DependencyInjectionTestExecutionListener.class })
public class AddUserAccountValidationTest extends AbstractJUnit4SpringContextTests {
@Autowired
@Qualifier("addAccountValidator")
private AddAccountValidator createAccountValidator;
@Test
public void allEntriesAreValid() {
CreateMyAccountForm member = new CreateMyAccountForm();
member.setAccountNumber("12323132312");
member.setDescription("Desc");
member.setEmail("user@gmail.com");
member.setEmailReenter("user@gmail.com");
member.setNickname("nick");
member.setFirstName("John");
member.setLastName("User");
member.setUsername("juser1");
member.setPassword("passw0rd");
member.setPasswordReenter("passw0rd");
member.setSecurityQuestion("1");
member.setSecurityAnswer("answer");
member.setZipCode("85021");
member.setSsnLast4("9999");
BindException errors = new BindException(member, "Member good");
createAccountValidator.validate(member, errors);
Assert.assertTrue("Errors found" + errors.getErrorCount(),
errors.getErrorCount() == 0);
}
@Test
public void noEntriesAreValid() {
MemberAccountModel member = new MemberAccountModel();
member.setDescription("Desc");
BindException errors = new BindException(member, "Member bad");
Set> violations = validator
.validate(member);
Assert.assertTrue(!violations.isEmpty());
}
@Test
public void accountNumberNotSet() {
CreateMyAccountForm model = new CreateMyAccountForm();
// model.setAccountNumber("12323132312");
model.setDescription("Desc");
model.setEmail("user@gmail.com");
model.setEmailReenter("user@gmail.com");
model.setNickname("nick");
model.setFirstName("User");
model.setLastName("Name");
model.setUsername("juser1");
model.setPassword("password");
model.setPasswordReenter("password");
model.setSecurityQuestion("1");
model.setSecurityAnswer("answer");
model.setZipCode("85021");
model.setSsnLast4("9999");
BindException errors = new BindException(model, "Member");
createAccountValidator.validate(model, errors);
Assert.assertEquals(1, errors.getErrorCount());
Assert.assertEquals("NotEmpty.memberAccount.accountNumber",
errors.getFieldError("accountNumber").getCode());
}
....

and so forth for testing all the permutations of supported validations.

UI testing

The variety and complexity of the UI technologies that can be used with Spring MVC can make such testing difficult, though for testing the UI of a portlet when deployed to a container, consider using a web testing product such as Selenium or WebTest, as with any other web application or portal.  Such testing is outside the scope of this post, but may be discussed in a future post.

Conclusions

Integration or unit testing for Portlet development is as useful and important as unit testing is for any other form of development. The biggest pain point I have found is that, once unit tests have been run to ensure a controller is working correctly, it then takes time to deploy a portal application into a container (like Liferay) before the UI interaction and service integration subtleties can be discovered.  However, in a test-driven development mindset, it will always be easier and more efficient for a developer to find any errors using tests that can be run through their IDE or automated into a continuous integration build cycle (such as with tools like Bamboo or Jenkins).  Additionally, the more detailed your test cases the more maintainable your portlet code will be.  They will be essential to have for future refactoring or enhancements.

References

Spring Portlet MVC
Spring MVC Showcase project
Portlets in Action book blog