Test a BMP entity bean

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

Problem

You want to test a bean-managed persistence (BMP) entity bean.

Background

If you are using entity beans, then why would you choose to use bean-managed persistence? Whereas there might in the past have been performance- or design- related reasons to use BMP entity beans, we have seen those issues melt away for the most part. You might have inherited an application that uses BMP entity beans because it has survived from the days before CMP began to perform acceptably on most application servers. Now nobody wants to change those entity beans because

“they work.”20 It is also possible that you have inherited a newer application, but one written by programmers that fell victim to the myth that CMP entity beans were “too slow.” Whatever the reason, a BMP entity bean has many more moving parts in it than a CMP entity bean, making it considerably more difficult to test. As a result, this recipe is among the most involved in the book. That just reflects the complexity of BMP: you have an EJB using JDBC to talk to a database. That means two layers of complexity to contend with, so it is no surprise that there is essen- tially double the work involved in building isolated tests around BMP entity beans.

Recipe

The strategy you can use to test a BMP entity bean depends on your ability to move the persistence code out of the entity bean and into other classes. First, we will consider what to do when you can refactor the BMP entity beans you need to test.

This makes testing BMP entity beans much simpler, because a BMP entity bean consists of little more than JDBC client code, JNDI lookups, and primary key man- agement. To that end, here is the general strategy:

1 Create a new class, which we will call the Bean Logic class.

2 Pick an EJB lifecycle method and identify the places where it performs a JNDI lookup or obtains the primary key.

3 Create a new method in the EJB that takes as parameters the primary key (if needed) and any objects the EJB looks up with JNDI.

20As soon as you are afraid of changing code, throw it away. You may not always be able to do this, but if you do it when you can, you will notice an improvement in the code you write. Trust us.

409 Test a BMP entity bean

4 Move this new method into the Bean Logic class and name it appropri- ately. You can make the code easier to read by naming the new method after the lifecycle method.

5 Presumably the Bean Logic class now contains mostly JDBC client code, which you can test using the techniques in chapter 10, “Testing and JDBC.”

6 Use a mock objects approach to verify that the BMP entity bean correctly supplies the primary key to any Bean Logic method that requires it. This recipe relies rather heavily on mock objects, so if you are not already familiar with them, read essay B.4, “The mock objects landscape,” and then visit the EasyMock site (www.easymock.org) to help you get started.

7 Create a Deployment Test—which will have to run within the container—

to verify that the BMP entity bean retrieves its data source correctly.

8 Use a mock objects approach to test any programmatic security the bean might perform. The most direct approach is to instantiate the entity bean, give it a mock EntityContext and a fake version of the Bean Logic class, and then invoke the various lifecycle methods and verify that they per- form the appropriate security checks.

To illustrate this strategy, let us return to our Coffee Shop application. We have a BMP entity bean that represents order information: an order has an ID and belongs to a customer. (An order also contains order items, but we ignore that for now to simplify the example.) Imagine that you have inherited a “junk-drawer”

BMP entity bean: one that does everything directly inside its lifecycle methods.

(See solution A.7, “Test a BMP entity bean,” for the complete source of the origi- nal entity bean.) Testing this bean requires a live container, Cactus, setting up live test data in a database—all this is much more complex than it needs to be. Apply- ing the technique of this recipe, we move the vast majority of the entity bean code into a new class, which we name OrderBmpBeanLogic. To illustrate how little is left in the entity bean, here is what remains of the method ejbLoad():

public void ejbLoad() throws EJBException, RemoteException { logic.ejbLoad(getOrderId());

}

It really does not get much simpler than that. The variable logic stores an instance of the class OrderBmpBeanLogic. The method getOrderId() reveals the intent behind retrieving the primary key, because the primary key is the order ID:

private Integer getOrderId() {

return (Integer) context.getPrimaryKey();

}

410 CHAPTER 11

Testing Enterprise JavaBeans

Returning to ejbLoad(), notice the difference between this method’s signature and that of the Bean Logic class’s ejbLoad() method: the Bean Logic class’s version of the method takes the order ID as a parameter. Managing the primary key remains the entity bean’s job, so that code stays with the entity bean. This makes it easier to test the Bean Logic class because the test can provide the order ID directly, rather than use an EntityContext, something else the EJB container instantiates (and not us). As it stands, the Bean Logic class—despite the fact that it is called a bean logic class—does not depend at all on EJBs or an application server. We merely call it the “Bean Logic class” in the absence of a better name.21 As it stands, OrderBmpBean.ejbLoad() looks to be too simple to break, so we do not need to test it.

The only way it can fail is if either the primary key is incorrect or if OrderBmpBean- Logic.ejbLoad() is broken.

Now the only way the primary key could be incorrect would be if the EJB con- tainer were broken. The EJB container provides the entity context, so unless we forget to invoke EntityContext.getPrimaryKey(), we will have our primary key.

Well, then, our next test should verify that we do not forget to invoke that method.

Listing 11.10 shows the test.

package junit.cookbook.coffee.model.ejb.test;

import javax.ejb.EntityContext;

import javax.naming.*;

import javax.naming.Context;

import javax.sql.DataSource;

import junit.cookbook.coffee.model.ejb.OrderBmpBean;

import junit.framework.TestCase;

import org.easymock.MockControl;

import org.mockejb.jndi.MockContextFactory;

public class OrderBmpBeanTest extends TestCase { private OrderBmpBean bean;

private Object actualPrimaryKey;

protected void setUp() throws Exception { MockContextFactory.setAsInitial();

new InitialContext().bind(

"java:comp/env/jdbc/OrderData", mockDataSource);

21If you think of a better name, then use it. Do not inherit our laziness.

Listing 11.10 OrderBmpBeanTestTE AM FL Y

Team-Fly®

411 Test a BMP entity bean

bean = new OrderBmpBean();

}

public void testGetOrderId() throws Exception { Integer orderId = new Integer(0);

MockControl entityContextControl =

MockControl.createNiceControl(EntityContext.class);

EntityContext mockEntityContext =

(EntityContext) entityContextControl.getMock();

mockEntityContext.getPrimaryKey();

entityContextControl.setReturnValue(orderId);

entityContextControl.replay();

bean.setEntityContext(mockEntityContext);

assertEquals(orderId, bean.getOrderId());

entityContextControl.verify();

} }

Here we have used EasyMock to create a mock EntityContext. We then instanti- ate the EJB implementation class (OrderBmpBean), invoke setEntityContext() passing in our mock entity context, and then verify the value returned by getOr- derId(). To be certain that this value is not just hard coded somewhere, we take advantage of EasyMock’s API for verifying method invocation sequences. We set up the mock entity context, “record” the expected invocation of getPrimaryKey(), and then pass the mock entity context to our entity bean. When we invoke ver- ify() at the end of the test, the mock entity context verifies that its getPrima- ryKey() method was indeed invoked once. When we execute this test, it passes, so we can conclude that OrderBmpBean correctly retrieves the primary key from its entity context. We can further conclude that as long as OrderBmpBeanLogic.ejb- Load() works, then so will OrderBmpBean.ejbLoad(), as the latter merely invokes the former. You can test OrderBmpBeanLogic entirely outside the container, using the techniques in the first part of this book. Rather than complicate this discus- sion, we refer you to solution A.7, “Test a BMP entity bean,” to see the final refac- tored version of our entity bean and its collaborators, including some of its tests.

The next step is to verify that our entity bean correctly finds the data source in a JNDI directory.

If the data source is bound to part of the global JNDI namespace, then we can use MockContext, part of MockEJB, to verify that that entity bean looks up the data source using the correct JNDI name. This would be the test:

412 CHAPTER 11

Testing Enterprise JavaBeans

public void testLookupDataSource() throws Exception {

assertSame(mockDataSource, OrderBmpBean.lookupDataSource());

}

We deploy a MockDataSource at the expected JNDI name so that the entity bean will retrieve it, rather than be forced to check the contents of the live JNDI direc- tory. Yes, we do need to verify that the production JNDI directory has the data source, but not for this test. See recipe 11.13 for details on testing the production JNDI service. Finally, we make lookupDataSource() publicly available so we can eas- ily test it. This is one advantage of EJBs: we can make methods public at will; and as long as they do not show up on an EJB interface, only our tests will ever invoke them. There is a slight problem, however, if our entity bean uses a resource refer- ence to look up the data source. See the Discussion section of this recipe for how to test looking up JNDI resources outside the global JNDI namespace.

That would appear to be all for this entity bean. To summarize our approach, we extracted from the entity bean all the code except the code that depended directly on the EJB container: using the EntityContext and looking up resources in the JNDI directory. We tested the remaining EJB code using various mock object techniques, incorporating a mock entity context and a mock JNDI directory. What is left now is to test the Bean Logic class, a flexibly designed class that makes it easy to use Test Objects to test it in isolation from the database. See solution A.7, “Test a BMP entity bean,” if you are interested in seeing all the code involved.

Discussion

If your entity bean uses resource references, then you need to know a couple of extra details to use MockEJB properly. The first is relatively simple: deploy your mock object at the resource reference address, and not the JNDI name to which the reference resolves. For example, our entity bean might use the resource refer- ence jdbc/OrderData to refer to the data source deployed at jdbc/mimer/Coffee- ShopData in the global JNDI namespace. The entity bean would then look up the data source with the JNDI name java:comp/env/jdbc/OrderData. To deploy a mock data source for the entity bean, you must add your deploy at this address, and not at jdbc/mimer/CoffeeShopData. A future version of MockEJB will support resolving this resource reference for you, but in the meantime, it is not much of a problem.22 The second extra detail you need to know relates to a defect in J2EE 1.3.

We tried to use MockContext to deploy a mock data source to java:comp/env/

jdbc/OrderData, and then let the entity bean find the mock data source with this

22That is, a version after 0.5, which still does not support resolving resource references.

413 Test a BMP entity bean

JNDI name. It did not work. We asked MockEJB author Alexander Ananiev if there was anything we could do, and he told us about an apparently well-known defect in J2EE 1.3 that affects JNDI lookups outside the global namespace. The file j2ee.jar, part of the J2EE distribution, contains a copy of jndi.properties, the properties file used to configure the InitialContextFactory. This file specifies the property java.naming.factory.url.pkgs, and even if you try to override that property by invoking System.setProperty(), the initial context factory insists on delegating all nonglobal namespace lookups to the system default initial context factory, rather than the MockEJB context factory. This causes the test to look up resource refer- ences in a live JNDI directory, rather than the MockContext. To work around this problem, which has been fixed in J2EE 1.4, delete the file jndi.properties from your copy of j2ee.jar. You need to apply this workaround to any machine that might execute your tests. It is a drastic measure, but once we did it, our tests passed!

Because the file was removed in J2EE 1.4, it is safe to remove the file from your dis- tribution. Your application server vendor will use its own j2ee.jar with its own ven- dor-specific jndi.properties, anyway.

We admit that to someone new to JUnit, this looks like much more work than simply deploying the EJB and testing it in the container. We ought to mention that the merciless refactoring we performed to make the EJB easier to test did yield some utility classes that we can reuse for any entity bean, making it easier to create new ones and reducing the complexity of all BMP entity beans consider- ably. If, after reading this recipe, you remain unconvinced, then all we can do is say, “We tried,” and encourage you to try testing your BMP entity beans in a live container, against live data. We have spent a considerable amount of space (and time) in this book enumerating the drawbacks of testing all your database-aware code against a live database. The drawback to running tests inside a container should be equally clear. What you have not seen, however, are the hours of effort it took to write this recipe, due to problems with Cactus (a defect that had already been fixed in a more recent unreleased version), hot redeployment in JBoss (dynamic class-loading problems are difficult to recognize), and the other techni- cal challenges intrinsic to a complex test environment. The lesson is obvious:

keep it simple. Design your system so that the vast majority of your tests can run in a plain-vanilla JVM. Following this one piece of advice for all your testing is guar- anteed to save you a sizable amount of grief. Trust us.

Related

■ 11.13—Test the contents of your JNDI directory

■ Chapter 10—Testing and JDBC

414 CHAPTER 11

Testing Enterprise JavaBeans

■ A.7—Test a BMP entity bean

■ B.1—Too simple to break

■ MockEJB (www.mockejb.org)

■ EasyMock (www.easymock.org)

■ jMock (www.jmock.org), an alternative to EasyMock

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

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

(753 trang)