- •contents
- •preface
- •acknowledgments
- •about this book
- •Special features
- •Best practices
- •Design patterns in action
- •Software directory
- •Roadmap
- •Part 1: JUnit distilled
- •Part 2: Testing strategies
- •Part 3: Testing components
- •Code
- •References
- •Author online
- •about the authors
- •about the title
- •about the cover illustration
- •JUnit jumpstart
- •1.1 Proving it works
- •1.2 Starting from scratch
- •1.3 Understanding unit testing frameworks
- •1.4 Setting up JUnit
- •1.5 Testing with JUnit
- •1.6 Summary
- •2.1 Exploring core JUnit
- •2.2 Launching tests with test runners
- •2.2.1 Selecting a test runner
- •2.2.2 Defining your own test runner
- •2.3 Composing tests with TestSuite
- •2.3.1 Running the automatic suite
- •2.3.2 Rolling your own test suite
- •2.4 Collecting parameters with TestResult
- •2.5 Observing results with TestListener
- •2.6 Working with TestCase
- •2.6.1 Managing resources with a fixture
- •2.6.2 Creating unit test methods
- •2.7 Stepping through TestCalculator
- •2.7.1 Creating a TestSuite
- •2.7.2 Creating a TestResult
- •2.7.3 Executing the test methods
- •2.7.4 Reviewing the full JUnit life cycle
- •2.8 Summary
- •3.1 Introducing the controller component
- •3.1.1 Designing the interfaces
- •3.1.2 Implementing the base classes
- •3.2 Let’s test it!
- •3.2.1 Testing the DefaultController
- •3.2.2 Adding a handler
- •3.2.3 Processing a request
- •3.2.4 Improving testProcessRequest
- •3.3 Testing exception-handling
- •3.3.1 Simulating exceptional conditions
- •3.3.2 Testing for exceptions
- •3.4 Setting up a project for testing
- •3.5 Summary
- •4.1 The need for unit tests
- •4.1.1 Allowing greater test coverage
- •4.1.2 Enabling teamwork
- •4.1.3 Preventing regression and limiting debugging
- •4.1.4 Enabling refactoring
- •4.1.5 Improving implementation design
- •4.1.6 Serving as developer documentation
- •4.1.7 Having fun
- •4.2 Different kinds of tests
- •4.2.1 The four flavors of software tests
- •4.2.2 The three flavors of unit tests
- •4.3 Determining how good tests are
- •4.3.1 Measuring test coverage
- •4.3.2 Generating test coverage reports
- •4.3.3 Testing interactions
- •4.4 Test-Driven Development
- •4.4.1 Tweaking the cycle
- •4.5 Testing in the development cycle
- •4.6 Summary
- •5.1 A day in the life
- •5.2 Running tests from Ant
- •5.2.1 Ant, indispensable Ant
- •5.2.2 Ant targets, projects, properties, and tasks
- •5.2.3 The javac task
- •5.2.4 The JUnit task
- •5.2.5 Putting Ant to the task
- •5.2.6 Pretty printing with JUnitReport
- •5.2.7 Automatically finding the tests to run
- •5.3 Running tests from Maven
- •5.3.2 Configuring Maven for a project
- •5.3.3 Executing JUnit tests with Maven
- •5.3.4 Handling dependent jars with Maven
- •5.4 Running tests from Eclipse
- •5.4.1 Creating an Eclipse project
- •5.4.2 Running JUnit tests in Eclipse
- •5.5 Summary
- •6.1 Introducing stubs
- •6.2 Practicing on an HTTP connection sample
- •6.2.1 Choosing a stubbing solution
- •6.2.2 Using Jetty as an embedded server
- •6.3 Stubbing the web server’s resources
- •6.3.1 Setting up the first stub test
- •6.3.2 Testing for failure conditions
- •6.3.3 Reviewing the first stub test
- •6.4 Stubbing the connection
- •6.4.1 Producing a custom URL protocol handler
- •6.4.2 Creating a JDK HttpURLConnection stub
- •6.4.3 Running the test
- •6.5 Summary
- •7.1 Introducing mock objects
- •7.2 Mock tasting: a simple example
- •7.3 Using mock objects as a refactoring technique
- •7.3.1 Easy refactoring
- •7.3.2 Allowing more flexible code
- •7.4 Practicing on an HTTP connection sample
- •7.4.1 Defining the mock object
- •7.4.2 Testing a sample method
- •7.4.3 Try #1: easy method refactoring technique
- •7.4.4 Try #2: refactoring by using a class factory
- •7.5 Using mocks as Trojan horses
- •7.6 Deciding when to use mock objects
- •7.7 Summary
- •8.1 The problem with unit-testing components
- •8.2 Testing components using mock objects
- •8.2.1 Testing the servlet sample using EasyMock
- •8.2.2 Pros and cons of using mock objects to test components
- •8.3 What are integration unit tests?
- •8.4 Introducing Cactus
- •8.5 Testing components using Cactus
- •8.5.1 Running Cactus tests
- •8.5.2 Executing the tests using Cactus/Jetty integration
- •8.6 How Cactus works
- •8.6.2 Stepping through a test
- •8.7 Summary
- •9.1 Presenting the Administration application
- •9.2 Writing servlet tests with Cactus
- •9.2.1 Designing the first test
- •9.2.2 Using Maven to run Cactus tests
- •9.2.3 Finishing the Cactus servlet tests
- •9.3 Testing servlets with mock objects
- •9.3.1 Writing a test using DynaMocks and DynaBeans
- •9.3.2 Finishing the DynaMock tests
- •9.4 Writing filter tests with Cactus
- •9.4.1 Testing the filter with a SELECT query
- •9.4.2 Testing the filter for other query types
- •9.4.3 Running the Cactus filter tests with Maven
- •9.5 When to use Cactus, and when to use mock objects
- •9.6 Summary
- •10.1 Revisiting the Administration application
- •10.2 What is JSP unit testing?
- •10.3 Unit-testing a JSP in isolation with Cactus
- •10.3.1 Executing a JSP with SQL results data
- •10.3.2 Writing the Cactus test
- •10.3.3 Executing Cactus JSP tests with Maven
- •10.4 Unit-testing taglibs with Cactus
- •10.4.1 Defining a custom tag
- •10.4.2 Testing the custom tag
- •10.5 Unit-testing taglibs with mock objects
- •10.5.1 Introducing MockMaker and installing its Eclipse plugin
- •10.5.2 Using MockMaker to generate mocks from classes
- •10.6 When to use mock objects and when to use Cactus
- •10.7 Summary
- •Unit-testing database applications
- •11.1 Introduction to unit-testing databases
- •11.2 Testing business logic in isolation from the database
- •11.2.1 Implementing a database access layer interface
- •11.2.2 Setting up a mock database interface layer
- •11.2.3 Mocking the database interface layer
- •11.3 Testing persistence code in isolation from the database
- •11.3.1 Testing the execute method
- •11.3.2 Using expectations to verify state
- •11.4 Writing database integration unit tests
- •11.4.1 Filling the requirements for database integration tests
- •11.4.2 Presetting database data
- •11.5 Running the Cactus test using Ant
- •11.5.1 Reviewing the project structure
- •11.5.2 Introducing the Cactus/Ant integration module
- •11.5.3 Creating the Ant build file step by step
- •11.5.4 Executing the Cactus tests
- •11.6 Tuning for build performance
- •11.6.2 Grouping tests in functional test suites
- •11.7.1 Choosing an approach
- •11.7.2 Applying continuous integration
- •11.8 Summary
- •Unit-testing EJBs
- •12.1 Defining a sample EJB application
- •12.2 Using a façade strategy
- •12.3 Unit-testing JNDI code using mock objects
- •12.4 Unit-testing session beans
- •12.4.1 Using the factory method strategy
- •12.4.2 Using the factory class strategy
- •12.4.3 Using the mock JNDI implementation strategy
- •12.5 Using mock objects to test message-driven beans
- •12.6 Using mock objects to test entity beans
- •12.7 Choosing the right mock-objects strategy
- •12.8 Using integration unit tests
- •12.9 Using JUnit and remote calls
- •12.9.1 Requirements for using JUnit directly
- •12.9.2 Packaging the Petstore application in an ear file
- •12.9.3 Performing automatic deployment and execution of tests
- •12.9.4 Writing a remote JUnit test for PetstoreEJB
- •12.9.5 Fixing JNDI names
- •12.9.6 Running the tests
- •12.10 Using Cactus
- •12.10.1 Writing an EJB unit test with Cactus
- •12.10.2 Project directory structure
- •12.10.3 Packaging the Cactus tests
- •12.10.4 Executing the Cactus tests
- •12.11 Summary
- •A.1 Getting the source code
- •A.2 Source code overview
- •A.3 External libraries
- •A.4 Jar versions
- •A.5 Directory structure conventions
- •B.1 Installing Eclipse
- •B.2 Setting up Eclipse projects from the sources
- •B.3 Running JUnit tests from Eclipse
- •B.4 Running Ant scripts from Eclipse
- •B.5 Running Cactus tests from Eclipse
- •references
- •index
216CHAPTER 10
Unit-testing JSPs and taglibs
A test that can’t be repeated is worthless.
—Brian Marick
In this chapter, we’ll continue with the Administration application we introduced in chapter 9. In chapter 9, we focused on unit-testing the servlet component of the application. In this chapter, we concentrate on the view components—namely the JavaServer Pages (JSPs) and custom tag libraries (taglibs).
We’ll cover unit-testing JSPs and taglibs with both Cactus and mock objects. The two techniques are complementary. Mock objects excel at writing focused, fine-grained unit tests against the business logic. Meanwhile, Cactus can perform integration unit tests against the target environment. The integration unit tests are essential in order to ensure that all components work properly when run in their target containers.
10.1 Revisiting the Administration application
We’ll base our examples on the Administration application (introduced in chapter 9). Its architecture is shown in figure 10.1, which also highlights the parts for which you’ll write unit tests (shaded boxes).
You use the application by sending an HTTP request (from your browser) to the AdminServlet. You pass an SQL query to run as an HTTP parameter, which is retrieved by the AdminServlet. The security filter intercepts the HTTP request and verifies that the SQL query is harmless (that is, it’s a SELECT query). Then, the servlet executes the query on the database, stores the resulting objects in the HTTP Request object, and calls the Results View page. The JSP takes the results from the Request and displays them, nicely formatted, using custom JSP tags from your tag library.
HTTP request |
|
|
Security |
|
|
|
Admin |
|
|
DB |
|
||||
|
|
|
|
|
Filter |
|
|
|
Servlet |
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP response |
|
|
Results |
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
View |
|
|
|
Taglib |
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
||||
(HTML) |
|
|
|
|
|
|
|
|
|
||||||
|
|
JSP |
|
|
|
|
|
|
|
|
Figure 10.1 |
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Servlet Container |
|
|
|
Unit-testing the Results View JSP |
||||||
|
|
|
|
|
|
|
|
from the Administration application |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Unit-testing a JSP in isolation with Cactus |
217 |
|
|
10.2 What is JSP unit testing?
First, let’s remove any doubt: What we call unit-testing a JSP is not about unit-testing the servlet that is generated by the compilation of the JSP. We also assume that the JSP is well designed, which means there is no Java code in it. If the page must handle any presentation logic, the logic is encapsulated in a JavaBean or in a taglib. You can perform two kinds of tests to unit-test a JSP: test the JSP page itself in isolation and/or test the JSP’s taglibs.
You can isolate the JSP from the back end by simulating the JavaBeans it uses and then verifying that the returned page contains the expected data. We’ll use Cactus (see chapter 8) to demonstrate this type of test. Because mock objects (see chapter 7) operate only on Java code, you can’t use a pure mock-objects solution to unit-test your JSP in isolation.
You could also write functional tests for the JSP using a framework such as HttpUnit. However, doing so means going all the way to the back end of the application, possibly to the database. With a combination of Cactus and mock objects, you can prevent calling the back end and keep your focus on unit-testing the JSPs themselves.
You can also unit-test the custom tags used in the JSP. You’ll do this with both Cactus and mock objects. Both have pros and cons, and they can be used together effectively.
10.3 Unit-testing a JSP in isolation with Cactus
The strategy for unit-testing JSPs in isolation with Cactus is defined in figure 10.2.
Here is what happens. The Cactus test case class must extend ServletTestCase (or JspTestCase):
bIn the testXXX method (called by Cactus from inside the container), you create the mock
objects that will be used by the JSP. The JSP gets its dynamic information either from one con- tainer-implicit object (HttpServletRequest,
HttpServletResponse, or ServletConfig) or
Sets test objects
testXXX() b in Request, Session, etc.
Cactus C
ServletTestCase
endXXX() D JSP
Figure 10.2 Strategy to unit-test JSPs with Cactus
from a taglib. (We handle the taglib case in section 10.4.)
cStill in testXXX, you perform a forward to call the JSP under test. The JSP then executes, getting the mock data set up in b.
218CHAPTER 10
Unit-testing JSPs and taglibs
dCactus calls endXXX, passing to it the output from the JSP. This allows you to assert the content of the output and verify that the data you set up found its way to the
JSP output, in the correct location on the page.
10.3.1Executing a JSP with SQL results data
Let’s see some action on the Administration application. In chapter 9 (“Unittesting servlets and filters”), you defined that the results of executing the SQL query would be passed to the JSP by storing them as a collection of DynaBean objects in the HttpServletRequest object. Thanks to the dynamic nature of dyna beans, you can easily write a generic JSP that will display any data contained in the dyna beans. Dyna beans provide metadata about the data they contain. You can create a generic table with columns corresponding to the fields of the dyna beans, as shown in listing 10.1. The result of executing this JSP (using arbitrary SQL results data) is shown in figure 10.3.
Figure 10.3
Result of executing results.jsp with arbitrary data that comes from the execution of a SQL query
Listing 10.1 Results View JSP (results.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c"
uri="http://jakarta.apache.org/taglibs/core" %> <%@ taglib prefix="d" uri="/dynabeans" %>
<html>
<head>
<title>Results Page</title> </head>
<body bgcolor="white"> <table border="1">
<d:properties var="properties" |
b |
item="${requestScope.results[0]}"/> |
|
<tr>
<c:forEach var="property" items="${properties}">
Unit-testing a JSP in isolation with Cactus |
|
219 |
||
<th><c:out value="${property.name}"/></th> |
|
|
|
|
|
|
|
|
|
</c:forEach> |
|
|
|
|
</tr> |
|
|
|
|
<c:forEach var="result" items="${requestScope.results}"> |
|
|
|
|
<tr> |
|
|
|
|
<c:forEach var="property" items="${properties}"> |
|
|
|
|
<td><d:getProperty name="${property.name}" |
|
c |
|
|
|
|
|||
item="${result}"/></td> |
|
|
|
|
</c:forEach> |
|
|
|
|
</tr> |
|
|
|
|
</c:forEach> |
|
|
|
|
</table>
</body>
</html>
You use both JSTL tags and custom taglibs to write the JSP: The JSTL tag library is a standard set of useful and generic tags. It’s divided into several categories (core, XML, formatting, and SQL). The category used here is the core, which provides output, management of variables, conditional logic, loops, text imports, and URL manipulation. The JSTL implementation used is the Jakarta Standard 1.0 implementation (http://jakarta.apache.org/taglibs/) of the JSTL specifications (http://java.sun.com/products/jsp/jstl/).
You also write two custom tags (<d:properties> and <d:getProperty>), which are used to extract information from the dyna beans. <d:properties> (b) extracts the name of all properties of a dyna bean, and <d:getProperty> (c) extracts the value of a given dyna bean property.
There are two reasons for writing these custom tags. The primary reason is that it isn’t possible to extract dyna bean information without (ouch!) embedding Java code in the JSP (at least, not with the current implementation of the JSTL tags and the DynaBean package). The second reason is that it gives you a chance to write and unit-test custom taglibs of your own. (Of course, the Struts 1.1 tags are dyna- bean-aware, and you could use those, but we decided not to overload this chapter with yet another framework.)
10.3.2Writing the Cactus test
Now let’s write a Cactus ServletTestCase for the JSP. In chapter 9, you defined a method named callView from the AdminServlet class. The callView method forwards control to the Results View JSP, as shown in listing 10.2.
220CHAPTER 10
Unit-testing JSPs and taglibs
Listing 10.2 AdminServlet.callView implementation
package junitbook.pages;
[...]
import java.io.IOException;
public class AdminServlet extends HttpServlet
{
[...]
public void callView(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
request.getRequestDispatcher("/results.jsp")
.forward(request, response);
}
}
Listing 10.3 shows a unit test for callView that sets up the DynaBean objects in the Request, calls callView, and then verifies that the JSP output is what you expect.
Listing 10.3 TestAdminServlet.java: unit tests for results.jsp
package junitbook.pages;
import java.util.ArrayList; import java.util.Collection; import java.util.List;
import org.apache.cactus.ServletTestCase;
import org.apache.commons.beanutils.BasicDynaClass; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaProperty;
public class TestAdminServlet extends ServletTestCase
{
private Collection createCommandResult() throws Exception
{ |
Create test input |
List results = new ArrayList(); |
|
|
data for JSP |
DynaProperty[] props = new DynaProperty[] { |
|
new DynaProperty("id", String.class), |
|
new DynaProperty("responsetime", Long.class) |
|
};
BasicDynaClass dynaClass = new BasicDynaClass("requesttime", null, props);
DynaBean request1 = dynaClass.newInstance(); request1.set("id", "12345"); request1.set("responsetime", new Long(500));
Unit-testing a JSP in isolation with Cactus |
|
221 |
||
results.add(request1); |
|
|
|
|
|
|
|
|
|
DynaBean request2 = dynaClass.newInstance(); |
Create test |
|
||
request2.set("id", "56789"); |
|
|
||
request2.set("responsetime", |
new Long(430)); |
input data |
|
|
for JSP |
|
|||
results.add(request2); |
|
|
||
|
|
|
|
|
return results; |
|
|
|
|
}
public void testCallView() throws Exception |
|
|
||
{ |
|
|
|
|
AdminServlet servlet = new AdminServlet(); |
|
|
||
request.setAttribute("results", createCommandResult()); |
|
|
||
|
|
|||
servlet.callView(request, response); |
|
|
||
} |
|
|
|
|
public void endCallView( |
|
|
|
|
|
|
|
||
com.meterware.httpunit.WebResponse response) |
|
|
|
|
throws Exception |
|
|
|
|
{ |
|
|
|
|
assertTrue(response.isHTML()); |
|
|
|
|
assertEquals("tables", 1, response.getTables().length); |
|
|
|
|
assertEquals("columns", 2, |
|
|
|
|
response.getTables()[0].getColumnCount()); |
|
|
|
|
assertEquals("rows", 3, |
|
|
Use |
|
response.getTables()[0].getRowCount()); |
|
|
||
|
|
HttpUnit |
||
|
|
|
||
assertEquals("id", |
|
|
integration |
|
response.getTables()[0].getCellAsText(0, 0)); |
|
|
for asserting |
|
assertEquals("responsetime", |
|
|
HTTP |
|
response.getTables()[0].getCellAsText(0, 1)); |
|
|
response |
|
assertEquals("12345", |
|
|
|
|
response.getTables()[0].getCellAsText(1, 0)); |
|
|
|
|
assertEquals("500", |
|
|
|
|
response.getTables()[0].getCellAsText(1, 1)); |
|
|
|
|
assertEquals("56789", |
|
|
|
|
response.getTables()[0].getCellAsText(2, 0)); |
|
|
|
|
assertEquals("430", |
|
|
|
|
response.getTables()[0].getCellAsText(2, 1)); |
|
|
|
|
} |
|
|
|
|
}
You use the Cactus HttpUnit integration in the endCallView method to assert the returned HTML page. When Cactus needs to execute the endXXX method, first it looks for an endXXX(org.apache.cactus.WebResponse) signature. If this signature is found, Cactus calls it; if it isn’t, Cactus looks for an endXXX(com.meterware.httpunit.WebResponse) signature and, if it’s available, calls it. Using the