Friday, August 29, 2014

Unit Testing Portlets with Spring


A Simple Portlet
Suppose you had the following Portlet, which was taken from page 19 of Cameron Mckenzie's "Portlet Programming" (first edition).
import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class HelloWorldPortlet extends GenericPortlet {
   protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
      response.setContentType("text/html");
      response.getWriter().println("Hello Portlet");
   }

   @Override
   public String getTitle(RenderRequest request) {
      return "HelloWorldPortlet";
   }
}
One of the problems I had with unit testing portlets is that I couldn't get Spring to read the portlet deployment descriptor file (ie. portlet.xml). As documented in this StackOverflow post, I kept getting a NullPointerException whenever Spring tried to get the title of the portlet. The only really acceptable solution that I could come up with was to use a properties file. According to the documentation for GenericPortlet's getTitle method, the title of the portlet is retrieved from a ResourceBundle using 'javax.portlet.title' as the key.

This solution, however, isn't perfect. For starters, it's not getting the title from the portlet.xml file. (That's what I really want.) Next if I want the test and the actual production code to match up - that is, if I want the same title that shows up in production to also appear in the tests - I have to modify the portlet.xml file. Specifically, I need to notify the portlet container about the properties file I want to use:
<supported-locale>en</supported-locale>

<resource-bundle>baseName</resource-bundle>
The other problem that I had with this solution is that for whatever reason I needed two properties files; one in the source folder and the other in the test folder. Here's a picture of what I mean.

I did come up with a solution to this, but first I want to show what the current unit test looks like.
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;

import org.junit.Test;
import org.springframework.mock.web.portlet.MockPortletConfig;
import org.springframework.mock.web.portlet.MockRenderRequest;
import org.springframework.mock.web.portlet.MockRenderResponse;

public class HelloWorldPortletTest {

   @Test
   public void testDoView() {
      MockPortletConfig portletConfig = new MockPortletConfig();

      ResourceBundle resourceBundle = ResourceBundle.getBundle("baseName", Locale.US);
      portletConfig.setResourceBundle(Locale.US, resourceBundle);

      MockRenderResponse renderResponse = new MockRenderResponse();

      MockRenderRequest renderRequest = new MockRenderRequest();
      renderRequest.addPreferredLocale(Locale.US);

      GenericPortlet portlet = new HelloWorldPortlet();

      try {   
         portlet.init(portletConfig); // throws PortletException
         portlet.render(renderRequest, renderResponse); // throws IOException, PortletException
      } 
      catch (PortletException portletException) {
         portletException.printStackTrace();
      }
      catch (IOException ioException) {
         ioException.printStackTrace();
      }

      assertThat(renderResponse.getContentType(), equalTo("text/html"));

      try {
         String actualString = renderResponse.getContentAsString();
         String expectedString = "Hello Portlet" + System.getProperty("line.separator");

         assertThat(actualString, equalTo(expectedString)); // throws UnsupportedEncodingException
      }
      catch (UnsupportedEncodingException unsupportedEncodingException) {
         unsupportedEncodingException.printStackTrace();
      }
   }
}
The solution that I came up with, which I had alluded to earlier, was to explicitly state the location of the properties file. That is, I replaced this...
ResourceBundle resourceBundle = ResourceBundle.getBundle("baseName", Locale.US);
portletConfig.setResourceBundle(Locale.US, resourceBundle);
...with this...
try {
   File file = new File("src/baseName_en_US.properties");     
   InputStream inputStream = new FileInputStream(file); // throws FileNotFoundException
   InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
   ResourceBundle resourceBundle = new PropertyResourceBundle(inputStreamReader); // throws IOException
   portletConfig.setResourceBundle(Locale.US, resourceBundle); 
}
catch(FileNotFoundException fileNotFound) {
   fileNotFound.printStackTrace();
}
catch(IOException io) {
   io.printStackTrace();
}
Portlet with multiple Multi-Mode
You'll notice that the previous portlet only had the VIEW mode implemented. So how would you test a portlet with multiple modes like the following which was taken from page 133 of Cameron McKenzie's book.
public class MultiModePortlet extends GenericPortlet {

   @Override
   protected void doView(RenderRequest request, RenderResponse response)
         throws PortletException, IOException {
    
      response.setContentType("text/html");

      PrintWriter printWriter = response.getWriter();
      printWriter.println("This is the View mode.");
   }

   @Override
   protected void doEdit(RenderRequest request, RenderResponse response)
         throws PortletException, IOException {
    
      response.setContentType("text/html");

      PrintWriter printWriter = response.getWriter();
      printWriter.println("This is the Edit mode.");
   }

   @Override
   protected void doHelp(RenderRequest request, RenderResponse response)
         throws PortletException, IOException {
    
      response.setContentType("text/html");

      PrintWriter printWriter = response.getWriter();
      printWriter.println("This is the Help mode.");
   }
}
If you want to test a specific mode you have to set it on the MockPortletRequest object:
renderRequest.setPortletMode(PortletMode.VIEW);
Here's what the unit test for the Edit mode looks like:
@Test
public void testDoEdit() {
   MockPortletConfig portletConfig = new MockPortletConfig();

   ResourceBundle resourceBundle = ResourceBundle.getBundle("baseName", Locale.US);
   portletConfig.setResourceBundle(Locale.US, resourceBundle);

   MockRenderResponse renderResponse = new MockRenderResponse();

   MockRenderRequest renderRequest = new MockRenderRequest();
   renderRequest.addPreferredLocale(Locale.US);
   renderRequest.setPortletMode(PortletMode.EDIT);

   GenericPortlet portlet = new MultiModePortlet();

   try {   
      portlet.init(portletConfig); // throws PortletException
      portlet.render(renderRequest, renderResponse); // throws IOException, PortletException
   } 
   catch (PortletException portletException) {
      portletException.printStackTrace();
   }
   catch (IOException ioException) {
      ioException.printStackTrace();
   }

   assertThat(renderResponse.getContentType(), equalTo("text/html"));

   try {
      String actualString = renderResponse.getContentAsString();
      String expectedString = "This is the Edit mode." + System.getProperty("line.separator");

      assertThat(actualString, equalTo(expectedString)); // throws UnsupportedEncodingException
   }
   catch (UnsupportedEncodingException unsupportedEncodingException) {
      unsupportedEncodingException.printStackTrace();
   }
}

No comments:

Post a Comment