I recently saw a post about testing Spring MVC controllers outside of a container.  I’ve been working at OpenCredo on a portlet application over the last few months, and although a bit late in the day for this project, I figured something along these lines would be of great benefit for any future development.  So taking some of the ideas off of that blog, I started putting together a similar concept for use with the Spring Portlet MVC.

The goal of the exercise was to test a bit further around the portlet controllers; its one thing to write a unit test, to check a particular method is working as expected, but in a controller with 5 or 6 different action methods, I wanted to ensure that for a known set of parameters in the request, the correct controller method would be executed.

Mocking up a ContextLoader

The first step is creating a 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 BeanFactoryPostProcessor() {

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

beanFactory.registerResolvableDependency(DispatcherPortlet.class, dispatcherPortlet);
beanFactory.registerResolvableDependency(ViewResolver.class, viewResolver);

}

});

This is a simple ViewResolver implementation that 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.

Some example tests

I found a Spring portlet application project to use as a basis for some tests, and made a few tweaks. The complete source code can be found in GitHub.  This gives us a BooksController

@Controller(“bookController”)
@RequestMapping(“VIEW”)
@SessionAttributes(“book”)
public class BooksController {

private BookService bookService;
@Autowired
public BooksController(@Qualifier(“bookService”) BookService bookService) {

this.bookService = bookService;

}

}

along with its autowired service

@Service(value=”bookService”)
public class BookServiceImpl extends ApplicationObjectSupport implements BookService {

}

To test this controller I created a JUnit class and injected the DispatcherPortlet and ViewResolver from the application context.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:applicationContext-test.xml”},

loader=MockWebApplicationContextLoader.class)

@MockWebApplication(name=”booksPortlet”)
public class BooksControllerIntegrationTest {

@Autowired
private DispatcherPortlet dispatcherPortlet;
@Autowired
private MockViewResolver viewResolver;
@Autowired
@Qualifier(“bookService”)
private BookService bookService;

}

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

private void setupRequestSession(PortletRequest request) {

Book book = new Book(AUTHOR, TITLE, COUNT);
book.setKey(KEY);
PortletUtils.setSessionAttribute(request, “book”, book);

}

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 our example, there are four render methods in the controller under test; setting no extra parameters in the render request should call the viewListOfBooks method.  We can test this simply:

@Test
public void viewListOfBooks() throws ServletException, IOException, PortletException {

MockRenderRequest request = new MockRenderRequest();
setupRequestSession(request);
dispatcherPortlet.render(request, new MockRenderResponse());

}

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

@Test
public void viewBook() throws ServletException, IOException, PortletException {

final Integer TEST_ID = 2;
MockRenderRequest request = new MockRenderRequest();
setupRequestSession(request);
request.addParameter(“book”, TEST_ID.toString());
request.addParameter(“action”, “viewBook”);
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 use our MockViewResolver:

assertEquals(“Incorrect view name found”, “books”, 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 books found in model”, model.get(“books”));
assertTrue(“List of books found in model was not a Sorted Set”, model.get(“books”) instanceof SortedSet);
SortedSet<Book> modelBooks = (SortedSet<Book>) model.get(“books”);
assertEquals(“Incorrect number of books found in model”, bookService.getAllBooks().size(), modelBooks.size());

Testing Action Methods

The action methods generally require more request parameters to be added. For instance, the deleteBook method

@RequestMapping(params = “action=deleteBook”)
public void deleteBook(ActionResponse response, @RequestParam(“book”) Integer id) {

bookService.deleteBook(id);

}

will need request parameters

request.addParameter(“action”, “deleteBook”);
request.addParameter(“book”, “2″);

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

@RequestMapping(params = “action=addBook”)
public void submitAddBookFormPage(ActionRequest request, ActionResponse response, @ModelAttribute(“book”) Book book,

BindingResult result, @RequestParam(“_page”) int currentPage, Model model) {

}

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

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

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

Testing the flow from Action to Render methods

One last thing we can do is to run an action, take the render parameters set by the action, and check the correct render method is then called. Using the editBook 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 book service to ensure the action has completed as expected.

Conclusions

Hopefully this solution for integration testing will help people who are currently struggling with Portlet 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 before the exact workings can be determined.  Whilst tools such as Open Portal can help with this, it will still be easier and much more efficient for a developer to find any errors using tests that can be run through their IDE.

References

I was initially inspired by this post about writing integration tests for MVC controllers.

Whilst looking for portlet code examples – something that seems to be sorely lacking – I stumbled across the Portlets in Action blog and a related article on annotated controllers, which helped me further my knowledge of the various annotations.