5.10 Submitting Form Data

5.10.1 Problem

You want to write a test that submits your HTML forms and verifies the forms functionality.

5.10.2 Solution

Set parameters on the WebForm using its setParameter( ) method. Then simulate clicking a button by asking for one of the form's buttons and submitting it using the WebConversation instance.

5.10.3 Discussion

You fill in form field values using the setParameter( ) method on a WebForm instance. This simulates what the user would do if he was filling out a form in a web browser. You then ask the form for a WebRequest object, passing in the name of one of the submit buttons. All of this is shown in Example 5-9.

Example 5-9. Submitting a form
public void testSubmitSubscriptionWithoutRequiredField(  )
        throws Exception {
    WebForm form = getBlankSubscriptionForm(  );
    form.setParameter("nameField", "Eric Burke");
    WebRequest request = form.getRequest("subscribeBtn");

    // Submit the page. The web app should return us right back to
    // the subscription page because the Email address is not specified
    WebResponse response = this.webConversation.getResponse(request);

    // make sure the user is warned about the missing field
    String pageText = response.getText(  );
    assertTrue("Required fields warning is not present",
            pageText.indexOf("Email address is required") > -1);

    // make sure the nameField has the original text
    form = response.getFormWithID("subscriptionForm");
    assertEquals("Name field should be pre-filled",
            "Eric Burke", form.getParameterValue("nameField"));
}

The comments in Example 5-9 explain what is expected at each step. The overall goal is to ensure that the form treats the email address as a required field. If the field is missing, the form should be redisplayed with an error message. When the form is redisplayed, the name field should be pre-filled with the previously entered value.

Example 5-10 shows the updated servlet. As is typical in a web application, the validation logic is contained within the servlet, rather than the JSP. Even better, you might want to refactor the validation logic into a helper class rather than the servlet itself. This step would allow you to write standalone tests against the validation logic without invoking the servlet. Once the request is fully validated, the servlet dispatches to the JSP for rendering.

Example 5-10. Servlet with validation logic
public class NewsletterServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req,
            HttpServletResponse res) throws ServletException, IOException {
        dispatchToSubscriptionPage(req, res);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        if (req.getParameter("subscribeBtn") != null) {
            handleSubscribeButton(req, res);
        } else if (req.getParameter("unsubscribeBtn") != null) {
            // @todo - handle this later, but only after writing more tests
        }
        dispatchToSubscriptionPage(req, res);

    }

    private void dispatchToSubscriptionPage(HttpServletRequest req,
            HttpServletResponse res) throws ServletException, IOException {
        RequestDispatcher dispatcher =
                req.getRequestDispatcher("subscription.jsp");

        dispatcher.forward(req, res);
    }

    private void handleSubscribeButton(HttpServletRequest req,
            HttpServletResponse res) throws ServletException, IOException {
        String name = req.getParameter("nameField");
        String email = req.getParameter("emailField");

        // email is required
        if (email == null || email.trim().length(  ) == 0) {
            req.setAttribute("errorMessage",
                    "Email address is required");
            dispatchToSubscriptionPage(req, res);
        } else {
            // @todo - subscribe the user!
        }
    }
}

The NewsletterServlet is nearly at its final form. A doPost( ) method was added to handle the form submission, and the logic formerly found in doGet( ) has been refactored into the dispatchToSubscriptionPage( ) method. This refactoring avoids code duplication and is easily tested with the existing suite of unit tests.

Pay particular attention to the @todo comments. These indicate that portions of the code are not complete. With the test-first approach taken in this chapter, these pieces of functionality should not be written until the corresponding unit tests are written. You might also consider putting your @todo comments in your test cases, rather than in the code itself. This strategy provides stronger encouragement to focus on test-driven development when those features are eventually added.

Avoid the urge to write all of the functionality at once. Instead, work on tiny pieces of functionality with each new test. This process reduces the likelihood that you will procrastinate and skip some of the tests.

Finally, Example 5-11 shows the revised JSP. The JSP now contains logic to display the error message attribute, which is sometimes provided by the servlet. It also pre-fills the value of the name field if necessary.

Example 5-11. Revised JSP with some dynamic display
<html>
  <% String errorMsg = (String) request.getAttribute("errorMessage");
     String name = request.getParameter("nameField");
     if (name == null) {
         name = "";
     }
  %>

  <head>
    <title>Newsletter Subscription</title>
  </head>

  <body>
    <h1>Newsletter Subscription</h1>
    <% if (errorMsg != null) { %>
         <font color="red"><%= errorMsg %></font>
    <% } %>

    <form method="post" action="subscription" id="subscriptionForm">
        <table>
          <tr>
            <td>Name:</td>
            <td><input type="text" name="nameField"
                    value="<%= name %>"></td>
          </tr>
          <tr>
            <td>Email:</td>
            <td><input type="text" name="emailField"> (required)</td>
          </tr>
        </table>
        <input type="submit" name="subscribeBtn" value="Subscribe"/>
        <input type="submit" name="unsubscribeBtn" value="Unsubscribe"/>
    </form>
  </body>
</html>

At this point, the tests (including all of the old tests) should pass. It is also a good idea to try out the web app inside of a web browser to see if you forgot to test anything. The test-first process can continue until all of the functionality is implemented.

5.10.4 See Also

Recipe 5.8 and Recipe 5.9 show how to test other aspects of HTML forms.