Test updating the HTTP session object

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 483 - 487)

■ B.1—Too simple to break

12.2 Test updating the HTTP session object

Recipe

You want to verify the contents of an HTTP session, but the code that triggers updating the session is not available to invoke directly from a test.

Background

You will most typically arrive at this situation if you have inherited servlet code that was not designed to be tested easily. The methods that update a servlet ses- sion could be anywhere in the system, rather than refactored into a centralized service. This makes writing these tests a little more difficult than it needs to be.

Still, even if updating servlet session is done entirely within the servlet class itself, it might be hidden in non-public methods, meaning that you would need to either move those methods into another class or use some means of getting around the Java visibility rules in your tests.

453 Test updating the HTTP session object

Recipe

Fortunately, ServletUnit provides a way to gain access to the servlet session so that you can verify its contents. Returning to our Coffee Shop application, listing 12.2 shows the test we write to verify the contents of the HTTP session when putting 5 kilograms of Sumatra coffee beans into an empty shopcart.

package junit.cookbook.coffee.test;

import javax.servlet.http.*;

import junit.cookbook.coffee.CoffeeShopController;

import junit.cookbook.coffee.model.ShopcartModel;

import junit.framework.TestCase;

import com.diasparsoftware.java.util.Money;

import com.meterware.HttpUnit.*;

import com.meterware.servletunit.*;

public class AddToShopcartControllerTest extends TestCase { private static final String webApplicationRoot = "../CoffeeShopWeb/Web Content";

public void testAddToEmptyShopcart() throws Exception { ServletRunner servletRunner =

new ServletRunner(

webApplicationRoot + "/WEB-INF/web.xml", "/coffeeShop");

String coffeeName = "Sumatra";

String coffeeProductId = "1";

int expectedQuantity = 5;

CoffeeShopController coffeeShopController = new CoffeeShopController();

coffeeShopController.init();

coffeeShopController.getCatalog().addCoffee(

coffeeProductId, coffeeName,

Money.dollars(7, 50));

WebRequest addToShopcartRequest = makeAddCoffeeRequest(

coffeeProductId, expectedQuantity);

ServletUnitClient client = servletRunner.newClient();

InvocationContext invocationContext =

client.newInvocation(addToShopcartRequest);

coffeeShopController.service(

invocationContext.getRequest(), invocationContext.getResponse());

Listing 12.2 Verifying the contents of the HTTP session

E

F

B

C

D

454 CHAPTER 12

Testing web components

ShopcartModel shopcartModel =

checkShopcartModel(invocationContext.getRequest());

assertEquals(

expectedQuantity,

shopcartModel.getQuantity(coffeeName));

}

public ShopcartModel checkShopcartModel(

HttpServletRequest request) {

HttpSession session = request.getSession();

assertNotNull(session);

ShopcartModel shopcartModel =

(ShopcartModel) session.getAttribute("shopcartModel");

assertNotNull(shopcartModel);

return shopcartModel;

}

private static WebRequest makeAddCoffeeRequest(

String coffeeProductId, int expectedQuantity) {

WebRequest addToShopcartRequest = new PostMethodWebRequest(

"http://localhost/coffeeShop/coffee");

addToShopcartRequest.setParameter(

"quantity-" + coffeeProductId, String.valueOf(expectedQuantity));

addToShopcartRequest.setParameter(

"addToShopcart-" + coffeeProductId, "Buy Now!");

return addToShopcartRequest;

} }

The general steps for writing such a test are:

Initialize the container—the ServletRunner—with your web deployment descriptor.

Instantiate and initialize the servlet, so that you can invoke its methods directly.

You might wonder why one does not ask the container for the servlet, as that is the container’s job. The “container” in this case provides context information, such as the servlet context root path, but it does not actually handle servlet requests. It is in this respect that it is a container simulator, rather than a lightweight container.

Create a request, for which you use HttpUnit’s WebRequest hierarchy: usually either a GetMethodWebRequest or a PostMethodWebRequest.

G

Always use local- host and port 80 with ServletUnit

B C

D

455 Test updating the HTTP session object

Ask the container for a ServletUnitClient from which you obtain an InvocationCon- text. It is this invocation context that provides access to the raw HTTP request and response that the servlet processes.

Invoke the servlet’s service() method passing the raw HTTP request and response as parameters, just as though the container were doing the work.

Ask the invocation context for the raw HTTP request, retrieve the HttpSession object, then verify its contents.

It is really only this last step that is specific to the needs of this test; you can use the others to build any servlet-based test with ServletUnit.

Discussion

If you are testing a servlet that you cannot change, then we recommend writing End-to-End Tests with HtmlUnit rather than Object Tests with ServletUnit. To jus- tify our recommendation, here are a few things we experienced while writing the test in this example, compared to the corresponding HtmlUnit test.

Because the ServletUnit test deals with raw HTTP requests, we entered some of the HTML element names incorrectly. When we read the test code to determine the problem, it was not clear which element name corresponded to which HTML form element. We find the corresponding HtmlUnit test (see chapter 13, “Testing J2EE Applications”) is clearer because we code it in terms of text fields and but- tons, rather than request parameters.

When we first tried to execute this test we found that we needed to involve Jas- per (a JSP compiler) and Ant (which shocked us). We needed this because our serv- let forwards to a JSP. We think that this is only more complexity without much gain, so we recommend you separate the act of choosing which JSP to forward to from the act of executing that forward operation. Doing so allows you to avoid the work (and expense) of actually compiling and “displaying” the JSP. Your test only needs to examine the HTTP session, so it might not even look at the rendered page, anyway.

The fact that the servlet forwards to a JSP also meant that we needed a real web deployment descriptor, rather than being able to register the servlet programmat- ically in the test. This separates test data from the test itself, which can make the test difficult to understand. If you do not need to forward or redirect to another URI/URL then you can invoke ServletRunner.registerServlet() to register your servlet. ServletUnit still provides you with the necessary invocation context to check your session object, but none of the web component-to-URL mapping you might expect would work. If your test does not need it, then do not worry about it.

E

F G

456 CHAPTER 12

Testing web components

The test is still quite long: almost fifty lines. Some of that is code that can be extracted into a test fixture (see recipe 3.4, “Factor out a test fixture”), and that includes statements stretching onto multiple lines, but even conceptually the test is “long.” It would be nice to focus on the one aspect of the test we really care about—updating the session.

Do not take this to mean that we dislike ServletUnit. Far from it. We intend these comments to mean that one should use ServletUnit judiciously, to test those aspects of web container interaction that cannot be extracted along with business logic. In other words, to test the “glue code” between your servlet and the code around it. When you first reach for ServletUnit, ask yourself whether you can extract the code in question and test it separately. If you honestly answer “no,”

then that is the time to use ServletUnit.

Before we leave this discussion, here is an advisory from the ServletUnit docu- mentation on using the invocation context feature. “Note first that you must do all of the processing that the service() method would have done if you take this approach. You may either call the service() method itself, or a combination of other calls that will prepare the response in the fashion you wish to test.” The pat- tern we have found most useful is to have doGet() or doPost() invoke process- Request(), then format the request (forward to JSP or write raw HTML). The method processRequest(), which we add, does all the real work. Using this little implementation pattern avoids rendering the JSP, which we leave to a different test (see recipe 12.3, “Test rendering a JavaServer Page”).

Related

■ 3.2—Create a separate source tree for test code

■ 3.3—Separate test packages from production code packages

■ 3.4—Factor out a test fixture

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 483 - 487)

Tải bản đầy đủ (PDF)

(753 trang)