Test infect your Enterprise JavaBeans

Learn how to test your J2EE components live and in the wild

1 2 Page 2
Page 2 of 2

The problem with unit testing EJBs

Now that you are sold on the benefits of unit testing, you must be warned about one problem: Unit tests run best when they run individually, in isolation, and quickly. A test case typically constructs the objects it is testing, as shown in the example above. However, sometimes the object being tested is dependent on the behavior of other objects. In such situations, the test case also builds a test harness to "stub out" the needed behavior. Such a test harness has the same interface as the real system, but doesn't actually do anything. For example, a test case for a robot arm might provide objects that behave like a motor and a position sensor. These would fool the unit being tested into thinking it was calling the real robot arm, even though nothing is moving. Although this works well for most standalone applications, it becomes outrageously complicated when you test code -- like an EJB -- that runs in an application server.

Indeed, EJBs are different. They are not meant to be constructed directly by their callers. They usually rely on context provided by their container, and without that context, most EJBs will not function. Although, conceivably, a test case could construct a convincing test harness, most likely the harness would be as complicated as the application server itself. In addition, you would have to test the test harness. And, since you've just built an application server, you might as well deploy your application on it. Otherwise, the testing and deployment environments would be different.

This problem also exists for other types of objects. Testing an RMI object, servlet, or any other component that gains context from its application server is just as difficult. If testing the object outside its native environment is even possible, the validity of the test might be compromised. Unit testing on the application server can be used for any of these cantankerous components.

Unit test cases on the application server

At this point you should understand the challenges of testing EJBs. We know unit tests run best when they run individually, in isolation, and quickly, but we also know that the objects we want to test lose valuable context without the application server. The answer to this challenge is to test each component individually, but to retain the context of the application server. This achieves the goals of unit testing, since we will have confidence that each component functions as designed. In addition, we will have a realistic test of the object in the appropriate context.

To unit test, the test cases must execute in the context of the application server. Since this white-box testing cannot be accomplished by a client connected from outside the application server, another mechanism is required. A servlet interface running tests seems to fit the bill, as all current application servers support servlets. The servlet interface executes within the application server, so all components will contain their context. The source code for the servlet interface is in TestRunner.java. (See Resources.)

TestRunner accepts HTTP GET requests with a parameter classname that identifies the test case class to run. In the same way that the command line interface does, the named class must either extend TestCase, or it must have a public, static method called suite() that returns a TestSuite. The servlet interface executes the tests in the test suite and reports its results as an HTML response to the HTTP GET request.

TestRunner uses the Class.forName() method to load the test case class. Therefore, the test cases must be present in the application server's classpath. In addition, TestRunner uses reflection to locate the suite() method, so it must be public.

An EJB test case

We will now revisit the telephone number class. The sources in example2 in Resources define a telephone number as a session bean. Since an EJB cannot be created directly by clients, we must modify the test case to match.

The first change you will notice is that the individual test methods no longer directly construct instances of the class under test. Instead, they call the createTelephoneNumber() method. (Of course, factoring object construction into a factory method is generally a good idea, anyway.)

Here is createTelephoneNumber():

   protected TelephoneNumber createTelephoneNumber(String areaCode, String
prefix, String number, String extension) throws RemoteException,
CreateException {
      return home.create(areaCode, prefix, number, extension);
   }

This method asks the EJBHome to create an instance of the telephone number session bean. Of course, before this can work, home must be assigned a value. This occurs in the test case's setUp() method. The JUnit framework calls setUp() on each new instance of the test case class before executing a test method. Each test method executes in its own new object to ensure it is isolated from every other test method.

For this test, the setUp() method has one responsibility: to locate the EJBHome interface for the telephone number EJB. Like any other EJB client, TelephoneNumberTests uses JNDI to find a reference to the home interface, as shown below:

   private TelephoneNumberHome home;
   public void setUp() throws Exception {
     Context initial = new InitialContext();
     Object objRef = initial.lookup("TelephoneNumber");
     home = (TelephoneNumberHome)PortableRemoteObject.narrow(objRef,
TelephoneNumberHome.class);
   }

The application server itself provides the JNDI InitialContext. This namespace is available only to components executing in the application server; therefore, this test case can run only in that context. In the root of this namespace you can find the EJBs known to the server. That means, of course, that the TelephoneNumber component must be identified to the application server. We'll take care of that later when we deploy the EJB.

Create the TestRunner J2EE component

At this point, you could deploy this servlet into virtually any application server available today. For best results, you should package this servlet as a Web component. A Web component is a specialized jar file that contains the class files and component definitions required to distribute servlets and JSPs. In this situation, the Web component will contain the JUnit framework and the TestRunner servlet. The actual EJB and test case will go into a separate package specifically for the bean. The J2EE SDK includes a tool called packager that creates the Web component archive (.war) file. packager assembles the class files, JSP files, XML deployment descriptor, and any other resources (such as HTML files or graphics) into the archive.

Figure 1. Create the Web component

The easiest way to start a Web component is with the J2EE deployment tool. deploytool features a wizard that will create a Web component archive based on your input. Unfortunately, it immediately puts the .war file into an application archive (.ear) file. You can use jar to extract the .war file from the .ear file. Next, use jar to extract the web.xml file from the .war file. The definition file informs the application server what servlets and JSPs should be exposed, the base URI for the component, and what security restrictions should be placed on the component. The specification file that accompanies this article defines and exposes one servlet: TestRunner.

While the deployment tool is a great way to get started, if you want to distribute the Web component independently of the entire application, you will need to use packager to create .war files independently of deploytool. A simple batch script can build the archive file for you.

Create the TelephoneNumber EJB

Now it's time to create the EJB package. As with the Web component, the easiest way to begin is with the deploytool. After you use deploytool to create the EJB, extract it from the application archive and pull out the XML specification file. You can then use that deployment descriptor in a batch file that will package the bean.

Deploy the J2EE components

After using the packager to create the Web component and the EJB, you can implement deploytool to bundle and deploy the components. deploytool builds J2EE applications out of their constituent parts. An application assembler combines all of the EJBs and Web components that make up the application. In addition, the assembler adds any supporting libraries to the package. Before deploying the application, the assembler also has an opportunity to set environment parameters, security roles and restrictions, and transactional dynamics. Once the package is complete, the application assembler deploys the application to a running J2EE server.

Figure 2. Assemble and deploy the enterprise application

Bear in mind that the SDK provided by Sun serves as a reference implementation of the J2EE specification. The packaging and deployment of J2EE applications may vary widely across different vendors' implementations. The specification explicitly allows for vendor differentiation in their tool sets.

Let's walk through the steps of deploying the JUnit servlet and the TelephoneNumberTests test case. This example uses the code and directory structure from http://images.techhive.com/downloads/idge/imported/article/jvw/2000/05/jw-0526-testinfect.zip in Resources.

  • Download and install the J2EE SDK.
  • Download and install testinfect.zip
  • Compile the JUnit code by running compile.bat.
  • Create the Web component by running package.bat.
  • Start deploytool and create a new application.
  • Add telephone.jar to the application as an EJB.
  • Add junit.war to the application as a Web component.
  • Run j2ee -verbose from a command line.
  • Identify the Context Root. Use JUnit. (This string will be the leading part of the servlet's URL.)
  • Identify the EJB's JNDI name. Use TelephoneNumber. (This must match the name used in the test case code.)
  • Tell deploytool to deploy the application you have just built to localhost. Do not bother with the client jar.

Now, JUnit is deployed. To verify this, point your Web browser to http://localhost:8000/JUnit. You should see a form that asks you for the name of the test suite class. Enter example2.TelephoneNumberTests here and choose "Submit Query." Congratulations, four out of the five tests failed! Let the debugging begin!

Michael Nygard is chief scientist of Javelin Solutions, a Minneapolis-based consulting firm. His experience runs the gamut, covering scientific, military, financial, educational, banking, and manufacturing applications. He still remembers the API upheaval from Java 0.9 alpha to beta. Michael is completely test infected. Tracie Karsjens is a senior consultant with Javelin Solutions, a Minneapolis-based consulting firm. She has been working in Java since 1996, and her experience covers every tier for enterprise Java applications. She is a strong advocate for extreme programming practices such as refactoring and unit testing for improving code. Tracie is a Sun Java-certified programmer and architect.

Learn more about this topic

1 2 Page 2
Page 2 of 2