JaVa
   

Testing Filters

The Servlet 2.3 specification introduced the concept of filters, reusable components that would execute before and/or after a given resource. Cactus, since version 1.2, includes a redirector and TestCase to facilitate the testing of filters and other components that depend on the implicit objects available in a filter. Before any filter tests can begin, the FilterRedirector must be mapped into the web.xml file:

<filter>
 <filter-name>FilterRedirector</filter-name>
 <filter-class> org.apache.cactus.server.FilterTestRedirector
 </filter-class>
</filter>
<filter-mapping>
 <filter-name>FilterRedirector</filter-name>
 <url-pattern>/FilterRedirector</url-pattern>
</filter-mapping>


Writing a Filter test case is, for the most part, straightforward. Complications arise from the fact that filters are meant to be executed in a chain. Each filter has the option of continuing the chain of execution by calling doFilter() on the FilterChain object that the container passes to it. Continuing (or discontinuing) the chain is an important part of filter behavior—good unit tests should verify that the chain of filters is handled correctly. At the time of this writing, the preferred method of verifying the chain behavior of the filter is to pass it a mock FilterChain and assert that the mock did or did not receive a call to its doFilter() method. The following example illustrates how to create and work with a mock FilterChain.

An Example

The code under test is a simple filter that verifies that a user is logged onto the server (it checks that a "User" object exists in the session). If the user record exists in the session, the filter continues the chain. If not, the filter breaks the chain and forwards to a login page specified in an initialization parameter.

package xptoolkit.cactus;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AuthFilter implements Filter {
 private FilterConfig config;
 public void init(FilterConfig config) throws ServletException {
 this.config = config;
 }
 public void doFilter(
 ServletRequest request,
 ServletResponse response,
 FilterChain chain)
 throws IOException, ServletException {
 String loginPage = config.getInitParameter("LOGIN_PAGE");
 HttpSession session = ((HttpServletRequest)request).getSession();
 Object user = session.getAttribute("USER");
 if(user != null) {
 chain.doFilter(request, response);
 } else {
 RequestDispatcher rd = request.getRequestDispatcher(loginPage);
 rd.forward(request, response);
 }
 }
 public void destroy() {}
}


The basics of writing a FilterTestCase are almost the same as those for writing a ServletTestCase. As with servlets, the filter in question must be created and initialized manually (usually in the setUp() method):

public void setUp()throws Exception {
 filter = new AuthFilter();
 filter.init(config);
 config.setInitParameter("LOGIN_PAGE", "/login_test.html");
 recordingChain = new RecordingChain();
}


The config variable of FilterTestCase stores the filter redirector's FilterConfig object (in wrapped form). The wrapper allows us to specify the initialization parameter "LOGIN PAGE" without having to add it to the web.xml entry for the FilterRedirector. In the case of AuthFilter, we need to verify the filter's behavior in two states: user logged in and user not logged in. Here are the two test methods:

public void testDoFilter() throws Exception{
 RecordingChain recordingChain = new RecordingChain();
 /*put "user" in session*/
 request.getSession().setAttribute("USER", "not a real user");
 filter.doFilter(request, response, recordingChain);
 assertTrue(recordingChain.verify());
}
public void testNoUser() throws Exception{
 RecordingChain recordingChain = new RecordingChain();
 /*no user in session*/
 filter.doFilter(request, response, recordingChain);
 assertTrue(!recordingChain.verify());
}


In both cases, we create an instance of a RecordingChain, which is a mock-type object invented for this test. FilterChain is easy to mock up because the interface contains only one method: doFilter(). RecordingChain implements FilterChain and adds a verify() method, which returns true or false depending on whether doFilter() was called on the chain. Both test methods call verify() to discover whether the chain was continued. Here is the source code:

class RecordingChain implements FilterChain{
 boolean doFilterInvoked = false;
 public void doFilter(ServletRequest servletRequest,
 ServletResponse servletResponse) {
 doFilterInvoked = true;
 }
 public boolean verify(){
 return doFilterInvoked;
 }
}


One final method asserts that the forward to the login page actually occurred. This is done by running assertions in endXXX() on the response returned from the testNoUser method:

public void endNoUser(WebResponse response){
 String text = response.getText();
 assertEquals("test login page contents", text);
}


Alternatively, we can use the Cactus HttpUnit integration to check the response:

public void endNoUser(com.meterware.httpunit.WebResponse response){
 /*
 page title is often a quick verification that the correct page is being examined.
 */
 String pageTitle = response.getTitle();
 assertEquals("Login Page", pageTitle);
}


If you need to test more complex behavior, you are free to implement FilterChain in such a way that it emulates more complex behavior (perhaps it writes some data to the response). The following listing contains the complete code for AuthFilterTest.

package xptoolkit.cactus;
import org.apache.cactus.*;
import junit.framework.*;
public class AuthFilterTest extends FilterTestCase{
 private AuthFilter filter;
 public AuthFilterTest(String name) {
 super(name);
 }
 public void setUp()throws Exception {
 filter = new AuthFilter();
 filter.init(config);
 config.setInitParameter("LOGIN_PAGE", "/login_test.html");
 recordingChain = new RecordingChain();
 }
 public void testDoFilter() throws Exception{
 RecordingChain recordingChain = new RecordingChain();
 /*put "user" in session*/
 request.getSession().setAttribute("USER", "not a real user");
 filter.doFilter(request, response, recordingChain);
 assertTrue(recordingChain.verify());
 }
 public void testNoUser() throws Exception{
 RecordingChain recordingChain = new RecordingChain();
 /*no user in session*/
 filter.doFilter(request, response, recordingChain);
 assertTrue(!recordingChain.verify());
 }
 public void endNoUser(WebResponse response){
 String text = response.getText();
 assertEquals("test login page contents", text);
 }
 public void tearDown(){
 /*no significant server-side resources to release*/
 }
 public static TestSuite suite(){
 TestSuite suite = new TestSuite(AuthFilterTest.class);
 return suite;
 }
 public static void main(String[] args){
 junit.textui.TestRunner.run(suite());
 }
}


JaVa
   
Comments