◆ Problem
You want to test the content of your JNDI directory as part of a Deployment Test suite.
◆ Background
Many of the J2EE testing techniques that we recommend throughout this part of the book have one goal: to minimize the amount of testing you do inside J2EE con- tainers. The logic is straightforward: the less you test inside the container, the more quickly the tests execute and the less complex your testing environment. In partic- ular, if fewer of your tests require a container, then the complexity of in-container
440 CHAPTER 11
Testing Enterprise JavaBeans
testing affects you less. If you have a problem with the in-container tests, then they do not block progress as much as they would if you did, say, all your business logic testing in the container. The idea is to minimize the impact of this complexity. In spite of this, you still eventually need to verify that you have configured the con- tainer correctly.
Suppose you use the MockEJB approach to testing a session bean that uses an entity bean, as we described in recipe 11.2. That recipe recommends using an in-memory JNDI directory so that your test can deploy a mock entity bean for the session bean to use. This way you avoid the complexity of deploying several EJBs just to test the one session bean. We like this approach; however, it is not enough to ensure that your session bean will work in production. It is a fact of J2EE-based software development that the JNDI directory is nothing more than a big, glorified Singleton, and that J2EE components use this Singleton all over the place. The fact that you can override this Singleton by setting JVM properties (as MockEJB does) does not change the fact that you need to verify that the production Single- ton is configured correctly. That is the problem we are trying to solve here.
◆ Recipe
Write a single test that verifies the content of the production JNDI directory by connecting to it and looking up all the objects you expect to find. This part is easy. Doing this effectively is the hard part, and we will get to that. First, let us con- sider an example from our Coffee Shop application. An early version of the appli- cation had only two objects in the JNDI directory: the business data source and a session bean for performing shopcart-based operations. We first wrote a simple test to perform the JNDI lookup on the JNDI name for our business data source, narrowed the object the directory returned, and then verified that it is indeed a DataSource object. Next we wrote a second test to do the same thing for the Shop- cartOperationsEJB. We extracted what the tests had in common into a method named doTestObjectDeployed(). Listing 11.19 shows the resulting code.
package junit.cookbook.coffee.deployment.test;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import javax.sql.DataSource;
import junit.cookbook.coffee.model.ejb.ShopcartOperationsHome;
import org.apache.cactus.ServletTestCase;
Listing 11.19 JndiDirectoryContentsTest
TE AM FL Y
Team-Fly®
441 Test the content of your JNDI directory
public class JndiDirectoryContentsTest extends ServletTestCase { public void testBusinessDataSource() throws Exception { doTestObjectDeployed(
"business data source",
"java:/jdbc/mimer/CoffeeShopData", DataSource.class);
}
public void testShopcartOperationsEjb() throws Exception { doTestObjectDeployed(
"shopcart operations EJB", "ejb/ShopcartOperations", ShopcartOperationsHome.class);
}
public void doTestObjectDeployed(
String jndiObjectDescription, String jndiName,
Class expectedClass) throws Exception {
Context context = new InitialContext();
Object jndiObject = context.lookup(jndiName);
String failureMessage = "Unable to find "
+ jndiObjectDescription + " at "
+ jndiName;
assertNotNull(failureMessage, jndiObject);
Object narrowedObject =
PortableRemoteObject.narrow(jndiObject, expectedClass);
assertTrue(expectedClass.isInstance(narrowedObject));
} }
This is a Cactus test, as it extends ServletTestCase. We did not need to make this a Cactus test in particular, but it does need to run inside the container, as not all objects are deployed to the global JNDI namespace. When we consulted the JBoss documentation, we learned that any data source we configure is only available inside the application server JVM, so to obtain the data source from the JNDI directory, we need to execute the test inside the application server JVM. Cactus is an easy way to make that happen—indeed, that is the point of Cactus. You could certainly just deploy this test and execute it from within a hand-crafted servlet, if you decided that you did not want to use Cactus, but we usually try to reuse the good work of others.
442 CHAPTER 11
Testing Enterprise JavaBeans
◆ Discussion
We included the JNDI object description to make the failure message more infor- mative. We will typically run this test right after deploying to a live application server, and if any object is not correctly deployed, we will want to know exactly which object with which the JNDI name is missing. Especially when we deploy to production, we want to be able to solve any configuration problems as quickly as possible, so we want as much information as we can get.
You will notice that as you add more tests to this test suite, each test is a one- liner: it invokes doTestObjectDeployed() with different parameters. You may think, “This ought to be a Parameterized Test Case,” as we described in recipe 4.8,
“Build a data-driven test suite.” Yes, it ought to be; however, Cactus 1.5 does not allow us to use the Parameterized Test Case technique. Cactus instantiates test cases on the server side, rather than using the test case objects we specify in the suite() method; there is no way to pass the test parameters into the server-side test objects. Perhaps by the time you read this, Cactus will have changed to accom- modate this approach, but if it has not, then your next best alternative is perhaps to generate the source code for this kind of test.
Remember, if you employ a mock objects approach to testing any J2EE compo- nent that uses JNDI, then all you have to do is verify that those objects are bound to the correct names in your JNDI directory, and those components will just work.
This is another example of isolating the expensive external resource to make test- ing easier.
◆ Related
■ 4.8—Build a data-driven test suite
■ Chapter 10—Testing and JDBC
443
Testing web components
This chapter covers
■ Testing HTTP session data
■ Testing static and dynamic web pages
■ Testing JSP components
■ Testing servlet code
■ Testing dynamic data before it is displayed
■ Using ServletUnit to simulate the web container
444 CHAPTER 12
Testing web components
In this chapter we provide recipes for testing web components in isolation, rather than testing the web application as a whole. We divided the chapters this way because we use different technologies and approaches to test a web application than we do to test its components—the servlets, JSPs, Velocity templates, and what have you that make up the application. In short, we test web applications from end to end using HtmlUnit, something described in detail in chapter 13, “Testing J2EE Applications,” but we use plain old JUnit and ServletUnit1 to test web compo- nents in isolation—that is, without a container and, if we can, without invoking any business logic. How do we do it?
We test business logic entirely outside the context of the web components that invoke it. It does not matter how complex this business logic is, nor which tech- nologies this business logic uses; we can test it without any mention whatsoever of a servlet or a JSP, so we do. As Mark Eames wrote to us, “If business logic is placed in Java objects that are tied to the web container or any container service, then that logic is only accessible within that container.” There is no good reason for business logic—by definition, something that belongs to the business—to be tied down to a particular application or its technology. We already implement the busi- ness logic in Java, constraining the business’s ability to use it in another context, so we prefer not to make it any worse than that.
To test business logic separately from the web components that invoke it requires some refactoring. Our general approach is as follows:
1 Write business logic entirely in terms of business objects.
2 Move business logic into an object that can execute outside the context of a servlet (the usual web application Controller).
3 Change the servlet to invoke the new, separate business logic object.
If you have existing servlet code whose business logic you need to test, extracting the business logic out of the servlet makes it possible to apply the “building block”
techniques we have discussed in part 1 without having to mess about with web com- ponent-related test tools. Keep those tools for the jobs they are designed to do. See recipe 12.1, “Test updating session data without a container,” for an example of the testing approach you can take when one extracts the business logic from a servlet.
1 Part of the HttpUnit project (http://httpunit.sourceforge.net). Do not confuse this with the defunct project of the same name.
445 Testing web components
Once you have isolated your business objects from the web components that use them you can use web component-related test tools to test them. At this point there are at least three viable options.
Test the components in a container
Your tests initialize a web container and invoke servlet methods as needed. These servlet methods are small and decoupled enough from the business logic that you can test what they do without invoking the business logic. If you want to use the business logic in your test, then please consult chapter 13, “Testing J2EE Applica- tions,” as those recipes involve testing the application more from end to end, rather than testing its parts in isolation.
Simulate the container
Rather than use a live web container, your tests can use lightweight container simu- lation to manage your web components. The two primary benefits of this approach are faster tests and more control. The tests execute more quickly because the simu- lated container does not provide all the same value-added features that an indus- trial-strength container provides. You have more control because the simulated container provides you with access to the HTTP objects that a production container would not provide. You can use these objects both to set up your test fixture and to verify the results. We generally prefer this approach to test handling a request and rendering a response.
Avoid the container
The web components we use in our web applications are just Java objects, so we can certainly test them without a container. The tricky part is knowing which parts of a servlet are easy to test without a container and which parts are just not worth the effort. The same is true of presentation layer technologies such as JSP or Velocity.
Our first instinct is to try to test a web component in isolation, but our experience told us when to throw in the towel and reach for a simulated container.
Our simulated container of choice is ServletUnit, which provides ServletRun- ner as its lightweight container, capable of processing your web deployment descriptor and registering servlets programmatically. In this chapter, whenever we identify the need to simulate the container we will use ServletUnit. The recipes are organized according to the component you need to test and the problems you might encounter along the way.
Finally, if you are stuck testing web components that you cannot refactor, but still want to write out-of-container tests, all is not lost. You can still use ServletUnit, and we provide some recipes that describe how.
446 CHAPTER 12
Testing web components