7.7 Submitting Form Data

7.7.1 Problem

You want to verify that your servlet correctly handles form parameters.

7.7.2 Solution

Write a ServletTestCase to simulate passing valid and invalid form parameters to a servlet.

Is Cactus Too Difficult?

Cactus provides a way to test server-side code running within a servlet container. A Cactus test, commonly known as an in-container test, can sometimes be overkill, depending on what you are testing. For example, this recipe shows how to use Cactus to test form parameters passed to a servlet. You may recall that Chapter 5 also shows how to test form parameters. In our opinion, HttpUnit is a lot easier to use than Cactus. Another approach, which some would argue is even easier, is to use a mock object. Specifically, you would have a mock implementation of the HttpServletRequest interface that provides the functionality you need to test request (form) parameters. So which is better? The answer lies in what you are comfortable using. If you are using Cactus for most of your tests, it may make sense to bite the bullet and use Cactus for all tests, even if the test is harder to write. On the other hand, if you do not mind mixing different unit testing frameworks in your project, then a mock implementation is probably the easiest solution.

7.7.3 Discussion

Most web applications have some sort of user input. A good example is a page for new customers to create an account. The page might ask for name, address, age, and gender. In the web world, this calls for an HTML form containing one or more input elements to solicit information from a user. The information contained in the input elements is added to the HTTP request by the browser when the user submits the form. As a servlet programmer, you must process each parameter and ensure that invalid information does not cause the servlet to crash or, even worse, corrupt a data store.

Start this recipe by looking at an example HTML form that is submitted to a servlet:[8]

[8] The HTML form shown here is not used by any part of our test.

<form method="post" action="LoginServlet">
  <table border="1">
    <tr>
      <td>Username:</td>
      <td><input type="text" name="username"/></td>
    </tr>
    <tr>
      <td>Password:</td>
      <td><input type="password" name="password"/></td>
    </tr>
    <tr>
      <td colspan="2" align="center" >
        <input type="submit" name="submit" value="Login"/>
      </td>
    </tr>
  </table>
</form>

When forms are submitted, the browser automatically adds each form field to the outgoing request. In the example above, the form data is sent to a servlet mapped to the name LoginServlet. The LoginServlet retrieves, verifies, and processes the data, which in this case is intended to authenticate a user. Example 7-1 shows the first iteration of the servlet.

Example 7-1. First iteration of the LoginServlet
package com.oreilly.javaxp.cactus.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class LoginServlet extends HttpServlet {

    /** 
     * Cactus does not automatically invoke this method. If you want to 
     * test this method then your test method must explicitly invoke it.
     */
    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {
        if (!validateParameters(req)) {
            req.setAttribute("errorMessage",
                             "Please enter your username and password");
            req.getRequestDispatcher("/login.jsp").forward(req, res);
            return;
        }

        // authenticate user
    }

    protected boolean validateParameters(HttpServletRequest req) {
        // @todo - implement this!
        return false;
    }
}

Our servlet overrides the doPost( ) method and immediately calls the validateParameters( ) method, which is the method we are going to test. First, we make the test fail, and then write the code to make it pass. Example 7-2 shows the next iteration of the Cactus test.

Example 7-2. Second iteration of the LoginServlet test
package com.oreilly.javaxp.cactus.servlet;

import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;

public class TestLoginServlet extends ServletTestCase {

    private LoginServlet servlet;

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

    public void setUp(  ) {
        this.servlet = new LoginServlet(  );
    }

    public void beginValidFormParameters(WebRequest webRequest) {
        webRequest.addParameter("username", "coyner_b", WebRequest.POST_METHOD);
        webRequest.addParameter("password", "secret", WebRequest.POST_METHOD);
    }

    public void testValidFormParameters(  ) {
        assertTrue("Valid Parameters.",
                   this.servlet.validateParameters(this.request));
    } 
}

The test method testValidFormParameters( ) fails because our servlet is hardcoded to return false. Now that we have seen our test fail, let's update the validateParameters( ) method to make our test pass. Example 7-3 shows the new and improved servlet code.

Example 7-3. Updated servlet
protected boolean validateParameters(HttpServletRequest req) {
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    if ((username == null || "".equals(username)) ||
        (password == null || "".equals(password))) {
        return false;
    } else {
        return true;
    }
}

Servlets must always check request parameters for null and an empty string. A parameter is null if the parameter does not exist in the request. A parameter contains an empty string when the parameter exists without a value. Example 7-4 shows how to test for these conditions.

Example 7-4. Improved unit test
package com.oreilly.javaxp.cactus.servlet;

import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;

public class UnitTestLoginServlet extends ServletTestCase {

    private LoginServlet servlet;

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

    public void setUp(  ) {
        this.servlet = new LoginServlet(  );
    }

    public void beginValidFormParameters(WebRequest webRequest) {
        webRequest.addParameter("username", "coyner_b", WebRequest.POST_METHOD);
        webRequest.addParameter("password", "secret", WebRequest.POST_METHOD);
    }

    public void testValidFormParameters(  ) {
        assertTrue("Valid Parameters.",
                   this.servlet.validateParameters(this.request));
    }

    public void beginUsernameParameterNull(WebRequest webRequest) {
        webRequest.addParameter("password", "secret", WebRequest.POST_METHOD);
    }

    public void testUsernameParameterNull(  ) {
        assertTrue("Username form field not specified in request.",
                   !this.servlet.validateParameters(this.request));
    }

    public void beginUsernameParameterEmptyString(WebRequest webRequest) {
        webRequest.addParameter("username", "", WebRequest.POST_METHOD);
        webRequest.addParameter("password", "secret", WebRequest.POST_METHOD);
    }

    public void testUsernameParameterEmptyString(  ) {
        assertTrue("Username not entered.",
                   !this.servlet.validateParameters(this.request));
    }

    public void beginPasswordParameterNull(WebRequest webRequest) {
        webRequest.addParameter("username", "coyner_b", WebRequest.POST_METHOD);
    }

    public void testPasswordParameterNull(  ) {
        assertTrue("Passord form field not specified in request.",
                   !this.servlet.validateParameters(this.request));
    }

    public void beginPasswordParameterEmptyString(WebRequest webRequest) {
        webRequest.addParameter("username", "coyner_b", WebRequest.POST_METHOD);
        webRequest.addParameter("password", "", WebRequest.POST_METHOD);
    }

    public void testPasswordParameterEmptyString(  ) {
        assertTrue("Password not entered.",
                   !this.servlet.validateParameters(this.request));
    }
}

7.7.4 See Also

Chapter 5 provides an alternate tool for testing server side code. Chapter 6 provides a discussion on mock objects.