7.11 Testing Servlet Filters

7.11.1 Problem

You want to test servlet filters.

7.11.2 Solution

Write a FilterTestCase class and assert that the filter continues down the chain or that the filter causes the chain to break. A mock FilterChain needs to be written to simulate filter-chaining behavior, too.

7.11.3 Discussion

Filters were introduced in Version 2.3 of the Servlet specification, and allow for preprocessing of the request and post-processing of the response. Filters act like an interceptor, in that they are executed before and after the servlet is called. Some common uses of filters are to perform logging, ensure that a user is authenticated, add extra information to a response such as an HTML footer, etc.

Example 7-12 shows how to test a filter that ensures a user is authenticated with the server. If the user is not authenticated with the server, she is redirected to a login page. The next recipe talks about how to setup an authenticated user in Cactus.

Example 7-12. Security filter
package com.oreilly.javaxp.cactus.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.Principal;

public class SecurityFilter implements Filter {

    public void init(FilterConfig config) {
    }

    public void doFilter(ServletRequest req,
                         ServletResponse res,
                         FilterChain chain)
            throws IOException, ServletException {

        Principal principal = ((HttpServletRequest) req).getUserPrincipal(  );
        if (principal == null) {
            req.setAttribute("errorMessage", "You are not logged in!");
            req.getRequestDispatcher("/login.jsp").forward(req, res);
        } else {

            // this is an instance of our MockFilterChain
            chain.doFilter(req, res);
        }
    }

    public void destroy(  ) {
    }
}

This filter is fairly simple. First we get the user principal from the request. If the principal is null, the user is not authenticated with the server, so we redirect the user to login screen. If a principal exists, we continue the filter chain.

Now let's write a Cactus test. Example 7-13 shows two tests. The first test ensures that if an authenticated user exists, the filter chain continues. The second test ensures that if an authenticated user does not exist, the filter chain breaks.

Example 7-13. Security filter test
package com.oreilly.javaxp.cactus.filter;

import org.apache.cactus.FilterTestCase;
import org.apache.cactus.WebRequest;
import org.apache.cactus.client.authentication.BasicAuthentication;

public class TestSecurityFilter extends FilterTestCase {

    private SecurityFilter filter;
    private MockFilterChain mockChain;

    public TestSecurityFilter(String name) {
        super(name);
    }

    public void setUp(  ) {
        this.filter = new SecurityFilter(  );
        this.mockChain = new MockFilterChain(  );
    }

    // this method runs on the client before testAuthenticatedUser(  )
    public void beginAuthenticatedUser(WebRequest webRequest) {
        webRequest.setRedirectorName("SecureFilterRedirector");
        webRequest.setAuthentication(
                new BasicAuthentication("coyner_b", "secret"));
    }

    // this method runs on the server
    public void testAuthenticatedUser(  ) throws Exception {
        this.mockChain.setExpectedInvocation(true);
        this.filter.doFilter(this.request, this.response, this.mockChain);
        this.mockChain.verify(  );
    }

    public void testNonAuthenticatedUser(  ) throws Exception {
        this.mockChain.setExpectedInvocation(false);
        this.filter.doFilter(this.request, this.response, this.mockChain);
        this.mockChain.verify(  );
    }
}

Filters are typically executed in a chain; each filter in the chain has the ability to continue or break the chain. A good filter test asserts that the chain either continues or breaks according to the filter's logic. The SecurityFilter continues the chain if an authenticated user exists in the request; otherwise, the chain breaks. The simplest way to test chaining behavior is with a Mock Object. This Mock Object needs to implement the FilterChain interface and set a flag to true if the doFilter( ) method is invoked. Example 7-14 shows how to create the mock object.

Example 7-14. Mock FilterChain
package com.oreilly.javaxp.cactus.filter;

import junit.framework.Assert;

import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class MockFilterChain implements FilterChain {

    private boolean shouldBeInvoked;
    private boolean wasInvoked;

    public void doFilter(ServletRequest req, ServletResponse res)
            throws IOException, ServletException {
        this.wasInvoked = true;
    }

    public void setExpectedInvocation(boolean shouldBeInvoked) {
        this.shouldBeInvoked = shouldBeInvoked;
    }

    public void verify(  ) {

        if (this.shouldBeInvoked) {
            Assert.assertTrue("Expected MockFilterChain to be invoked.",
                              this.wasInvoked);
        } else {
            Assert.assertTrue("Expected MockFilterChain filter not to be invoked.",
                              !this.wasInvoked);
        }
    }
}

7.11.4 See Also

Recipe 7.12 describes how to write a secure Cactus test. For more information on Mock Objects, see Chapter 6.