Published on dev2dev (http://dev2dev.bea.com/)
http://dev2dev.bea.com/pub/a/2005/10/mock_ejbs.html
See this if you're having trouble printing code examples
Enterprise JavaBeans (EJBs) are an important building block in a J2EE application and can give you a standard framework that allows service definition, event-driven processing, and object-relational persistence. However, a common complaint from those using EJBs is that their use makes unit testing of applications much more difficult. EJBs rely on the services of a container in order to run, but deploying beans into a container before unit testing them slows the process down and makes debugging more complicated. The recent, and much welcomed, rise in popularity of test-driven development has exacerbated this problem for many developers because of the rapid cycle of writing tests, writing production code, and refactoring that the approach implies.
This article introduces a library, MockEJB, which provides one possible solution to the problems of testing EJBs by allowing them to be tested inside or outside the EJB container. Built on top of existing mock object technology, MockEJB allows developers to develop and unit test their EJBs as plain Java objects (outside the EJB container) before later deploying them to the container for integration testing. Once in the container, you can use MockEJB to thoroughly test EJBs by controlling the beans' environment and allowing unexpected conditions to be simulated. The use of MockEJB can simultaneously make developers lives easier and more productive, while helping to achieve much more thorough test coverage than is often the case.
Enterprise JavaBeans (EJBs) are key building blocks in many large J2EE applications, and are used to define package services and define their interfaces (using session beans), create event handlers (with message-driven beans), and sometimes provide a persistence mechanism (via entity beans). Much of EJBs' power and benefit comes from the standardized runtime environment and services that the EJB container provides, including automatic thread and memory management, transaction management, and declarative security. However, because of this reliance on the container, EJBs cannot run outside it, and so this powerful runtime environment also causes problems when we come to unit test EJB components. How do we easily unit test something running inside the EJB container provided by an application server?
A number of approaches have been proposed for EJB testing, from simple EJB base classes that allow basic unit testing outside the container, to sophisticated test frameworks such as Cactus (see the Additional Reading section), which make it possible to run unit tests on EJBs running inside the container. More recently, the "Mock Objects" approach has emerged as an alternative way of unit testing code with complex environmental requirements, and MockEJB is an EJB-specific development of this idea.
Before diving into the details of MockEJB, it is worth recapping the background ideas and technologies it is based on.
Test-driven development (TDD) is an approach to software development (rather than testing) that places unit testing at the center of the process. Rather than writing large amounts of code and then doing as much unit testing as time allows, TDD turns the process on its head and only writes code in order to achieve a successful test. The process is highly iterative, involving a rapid cycle of writing a test, then writing the code required to make the test pass, and finally refactoring to improve the design if the new code means that the design is now less than optimal. The basic rules of TDD are that you write as many tests as possible, that all unit tests are automated, and that all tests must pass all the time. The Additional Reading section has some links to further information about TDD.
Although it's a simple idea, TDD has been found to be incredibly effective in practice, because the ever-growing unit test set acts to provide immediate feedback and validation of the code being written so that unpleasant surprises are not saved up to cause problems late in the development process. A later benefit of TDD is that it results in a large, reliable set of unit tests, which means developers can return to their code after a long period away from it (or indeed adopt other developers' code) and change it safely, relying on the unit tests to fail if an error is made during the changes.
One of the implications of achieving an effective TDD process is that developers need to use a development environment that allows them to write and execute tests very quickly against a constantly changing code base. The problem from the point of view of an EJB developer is that the deployment process required to allow an EJB to run in the container slows the compilation process down considerably and makes the development environment rather more complex. This can result in the dangerous situation of EJB developers having to "batch up" their tests and running larger sets of unit tests against the code less regularly. While still allowing unit testing, this reduces the speed of the feedback loop that the TDD approach relies upon. Therefore, to support TDD effectively, we would like to be able to eliminate the deployment cycle for our EJBs while they are being developed; using a "mock" EJB container is one of the ways we can achieve this.
Another challenge when using TDD for the development of a complex system is the difficulty of isolating the functionality under test from the rest of the system. Application components (usually packaged as session beans) normally rely on other components to provide services to them. In the unit test environment, we want to eliminate the dependencies of the component under test so that the services called return predictable test data instead of running the actual production code. Dependency on persistent data (represented by entity beans) is another manifestation of this problem. It may be desirable to develop the unit test in such a way that it does not depend on a database (which, even in a development environment, is often a shared resource, and is therefore difficult to control).
Mock objects (or "mocks") are a technology that can help you to replace complex or difficult-to-control runtime components with lightweight, controllable versions that are more amenable to a rapid unit test process. The idea of mock objects is to allow a unit test to replace part of the normal runtime environment with a specially written replacement, which implements the same interface as the regular component but can be easily controlled by the unit test code. Mock object implementations exist for parts of the java.sql JDBC classes, java.io IO library, java.net networking library, and many other pieces of the standard Java runtime environment. Such mock implementations allow us to simulate errors, test database code without the need to have a database present, test network code without needing a real server (or client), and so on. In short, mock objects make the unit testing process much more tractable for code that requires a complex runtime environment. MockEJB is a recent addition to the mock objects landscape, providing mock implementations of many of the important J2EE interfaces.
|
MockEJB is an EJB testing framework written by Alexander Ananiev. It combines a number of existing ideas and technologies that give the EJB developer a powerful testing framework for easy unit testing of EJBs, both inside and outside the EJB container. In essence, MockEJB provides mock object implementations of all the important J2EE interfaces required by an EJB and therefore provides a mock container implementation that developers can easily control from their unit tests. The mock container it provides is a plain Java object and allows EJBs to be tested as plain Java objects, outside the traditional container of a J2EE application server. However, MockEJB also integrates with the Cactus server-side testing framework and provides the EJB developer with a unit test environment inside the application server, if required.
This article focuses on the use of MockEJB for testing beans outside the application server. When used in this way, some of the key features that MockEJB provides include:
MockEJB is supplied as a Java library, with samples and javadoc documentation to guide its use. Like most libraries, the easiest way to come to grips with MockEJB is to use it to solve some simple problems, and so in the next section, we'll see how MockEJB can help you test some session beans.
To illustrate the use of MockEJB, we have developed a very simple application containing two session beans and one MDB. The zip file associated with this article contains a simple codeline comprising the source code for the beans under test, the unit test source code, and an Ant build script to build and test the beans. To build the code and run the tests, you will need to install Ant, JUnit, and MockEJB, as well as the JDK. You can get full instructions for using the example code in the README file within the codeline.
The EJBs provided have been developed to allow you to investigate some specific features of MockEJB, and are all extremely simple, providing a basic calculator via various service interfaces. Three beans are included:
SimpleCalc is the simplest bean, a stateless session bean offering a remote interface providing addition, subtraction, multiplication, and division services for double operands. This bean allows you to investigate MockEJB's support for simple EJB testing.ParsingCalc is another stateless session bean that offers a single calculate(String expression) operation. This bean parses the string expression passed to it to extract the operator and operands for the operation required, and calls the SimpleCalc bean to perform the calculations. This bean allows you to investigate MockEJB's support for inter-bean references.MessageCalc is a message-driven bean that listens for requests via a JMS queue, extracts the body of any text messages received, and uses the ParsingCalc bean to evaluate them, returning the result of the calculation to the JMSReplyTo destination of the request message. This bean allows you to investigate MockEJB's support for JMS and MDB.The general process for using MockEJB to test your beans is as follows:
MockContextFactory.setAsInitial()).InitialContext and MockContainer).com.mockejb.jms package and calling context.rebind() to associate them with a name in the mock JNDI directory).SessionBeanDescriptor) with constructor parameters that define the interfaces and bean instance to deploy.deploy() method can be called for each deployment descriptor object to deploy the beans and make them available for testing.Once this simple process is complete, the beans can be accessed via the standard J2EE lookup/narrow/invoke process that you are already familiar with.
To illustrate the process, let's start with the simplest possible example and examine how MockEJB allows us to test a simple independent session bean outside the container. The code fragment below shows how the bean is deployed to a local in-memory container for testing:
// Setup the JNDI environment to use the MockEJB
// context factory
MockContextFactory.setAsInitial();
// Create the initial context that will be used for binding EJBs
Context ctx = new InitialContext();
// Create an instance of the MockContainer
MockContainer mc = new MockContainer(ctx);
// Create deployment descriptor for our sample bean
// This is used instead of an XML descriptor
SessionBeanDescriptor dd = new SessionBeanDescriptor(
"java:comp/env/ejb/SimpleCalc",
SimpleCalcHome.class, SimpleCalc.class,
new SimpleCalcBean()) ;
// Deploy our bean to the container allowing it to
// be found via JNDI and tested
mc.deploy(dd);
This code has set up the environment to use MockEJB's mock JNDI implementation, rather than a container-based one, create a mock EJB container to deploy our bean to, and then deploy the bean to the container by describing it via a SessionBeanDescriptor object. This use of a Java object as a bean deployment descriptor is the main difference that you'll notice between MockEJB's container and a real application server EJB container. As you can see from the code, creating the descriptor for a bean is simple and just involves constructing it by specifying the JNDI name it should be bound to, the classes that provide the home and remote interface definitions, and a bean class instance to deploy.
Once the bean has been deployed to our mock container, we can write tests against it as if it were deployed to a conventional application server container. The JUnit test method below shows the code needed to call the business methods of our sample bean:
public void testTwoPlusTwo() throws Exception
{
// Look up the home
Context ctx = new InitialContext();
Object ejbObj =
ctx.lookup("java:comp/env/ejb/SimpleCalc");
// Narrowing is not needed with MockEJB or WebLogic, but
// we call it anyway for standardization
SimpleCalcHome home = (SimpleCalcHome)
PortableRemoteObject.
narrow(ejbObj, SimpleCalcHome.class);
SimpleCalc calc = home.create();
assertEquals("Add operator failure", 4.0,
calc.add(2.0, 2.0), 0.0) ;
}
As you can see, this code is identical to the code required to invoke operations on the bean if it were running within an application server container, and so no MockEJB-specific programming is required once the test beans have been deployed. In the sample code associated with the article, the TestSimpleCalcBean JUnit test case class contains this example code for testing the SimpleCalc bean. However, when looking at the sample code, note that that code to perform the MockEJB-specific initialization of the container and deployment of the beans has been factored out of the JUnit test case class into an abstract base class called MockBeanTestBase. This class gives you additional features (such as running inside the EJB container) and avoids duplication of code between tests.
|
The previous example is obviously extremely simple, as the bean itself uses no container-hosted resources, and so we could achieve the same affect by simply instantiating an object of the bean class and calling its methods. Let's see how MockEJB can help us to test a session bean that refers to other beans. In our example code, this is the ParsingCalc bean, which requires the services of the SimpleCalc bean in order to perform the calculations.
MockEJB provides the ability for EJBs to look up and reference each other easily, by simply deploying all the beans required to the mock container, associated with the right JNDI names. The sample code that illustrates this can be found in the TestParsingCalcBean JUnit class. The key part of the initialization is shown in the code fragment below:
// Initialize initial context and container here
// ...
// Create deployment descriptor for the bean under
// test.
SessionBeanDescriptor testDD =
new SessionBeanDescriptor(
"java:comp/env/ejb/ParsingCalc",
ParsingCalcHome.class, ParsingCalc.class,
new ParsingCalcBean()) ;
// Create deployment descriptor for the bean that
// the bean under test relies upon.
SessionBeanDescriptor depDD =
new SessionBeanDescriptor(
"java:comp/env/ejb/SimpleCalc",
SimpleCalcHome.class, SimpleCalc.class,
new SimpleCalcBean()) ;
// Deploy both beans to the mock container
mc.deploy(depDD) ;
mc.deploy(testDD);
(Note, again, that in the supplied sample code, the bean deployment is actually achieved using the deployRemoteSessionBean() method from the abstract base class that the JUnit test class extends. We are showing the required process explicitly in the code fragment above.)
Now, both beans are deployed to the mock container, and provided that the JNDI name that the SimpleBean is bound to ("java:comp/env/ejb/SimpleCalc" in the example above) matches the name that the ParsingCalc bean looks up in its initial context, MockEJB will return a reference to the correct EJB object. Some sample unit test code for the ParsingCalc bean can be found in the testParsingCalcOperations method of the example unit test class, where the bean is tested using regular EJB invocation. If you run the test, the ParsingCalc bean will successfully locate and invoke operations on the SimpleCalc bean.
This example illustrates the simplest use of MockEJB, although it still provides us with a very useful facility, namely the ability to develop, test, and debug our session beans outside the container. Still, we are not using any of the more complex facilities provided by the EJB container.
Let's now consider how we can test our MDB outside the container by using MockEJB's JMS facilities. Testing an MDB is obviously quite different from testing a session (or entity) bean in that the bean can only be exercised by sending messages to the appropriate JMS topic or queue that the bean is listening on. Therefore, while we still need to deploy the bean itself, most of the effort required to set up an MDB for testing is concerned with creating the JMS objects and connecting the bean to the right destination. Luckily, MockEJB makes it possible to achieve all this in a couple of lines of code. The code fragment below illustrates how to set up the environment to allow an MDB to be tested:
// Initialize initial context and container
MockContextFactory.setAsInitial();
Context ctx = new InitialContext();
MockContainer mc = new MockContainer(ctx);
String factoryName = "jms/QueueConnectionFactory" ;
String requestQueueName = "jms/queue/CalcRequestQueue" ;
String responseQueueName = "jms/queue/CalcResponseQueue" ;
// Create the mock JMS objects and bind them to
// the appropriate names in the mock JNDI directory
QueueConnectionFactory qcf =
new QueueConnectionFactoryImpl() ;
ctx.rebind(factoryName, qcf);
Queue queue = new MockQueue(requestQueueName) ;
ctx.rebind(requestQueueName, this.queue);
ctx.rebind(responseQueueName,
new MockQueue(responseQueueName));
// Create an instance of the bean under test and
// attach it to the test queue as a listener
MessageCalcBean beanObj = new MessageCalcBean() ;
((MockQueue)queue).addMessageListener(beanObj) ;
// Create a deployment descriptor for the MDB
// and set the flag indicating that the JMS
// objects are already created and bound to the
// specified names
MDBDescriptor mdbDD = new MDBDescriptor(
factoryName, requestQueueName, beanObj) ;
mdbDD.setIsAlreadyBound(true) ;
// Deploy the MDB ready for testing
this.container.deploy(mdbDD);
As you can see from the code snippet, most of the code involved in setting up the bean is involved with creating suitable JMS mock objects and binding them to the required names in the mock JNDI directory. Now, when the MDB looks up the queue connection factory, it will be passed a reference to the mock factory created above, and similarly, when it requests CalcRequestQueue, it will be passed a reference to our mock queue, allowing us to manipulate and monitor the queue as required. The example code corresponding to this process can be found in the deployQueueMessageDrivenBean() method in the MockBeanTestBase class.
We can now test our MDB by creating suitable (mock) JMS messages to send it, and dispatching them to the bean via our mock queue. A code fragment illustrating this is shown below:
// ... following from previous code fragment
// Provide us with access to the queues
QueueConnection conn = qcf.createQueueConnection();
QueueSession sess = conn.createQueueSession( false,
Session.AUTO_ACKNOWLEDGE);
// Create our test message
TextMessage reqMsg = session.createTextMessage();
// Look up the reply queue and set in the message
Queue replyQ = (Queue)ctx.lookup(RESPONSE_QUEUE_NAME) ;
reqMsg.setJMSReplyTo(replyQ) ;
// Start the connection and create a receiver to
// to begin receiving messages
conn.start();
QueueReceiver recv = sess.createReceiver(replyQ) ;
// Get rid of any messages that might be on the
// response queue because of failed tests
while(recv.receiveNoWait()!=null) ;
// Send a message to run a unit test
reqMsg.setText("2.0 + 2.0") ;
sender.send(reqMsg);
TextMessage reply1 =
(TextMessage)recv.receive(RECEIVE_TIMEOUT_SEC) ;
assertNotNull(reply1);
assertEquals("Wrong addition result",
"2.0 + 2.0 = 4.0", reply1.getText()) ;
In our test code, we can use the standard JMS API to look up the queues and connection factories and to create, send, and receive messages. Because we have used mock queue implementations, MockEJB's JMS implementation will process the message that we sent to the request queue, invoking our MDB's onMessage() method. This will block until the onMessage() method returns, and so we can immediately check for a response. The blocking (synchronous) nature of MockEJB's JMS implementation is a great convenience for unit testing, as it allows us to test the results of JMS-driven operations without having to deal with unpredictable delays and timeouts in the unit test code. It also allows us to run integration tests where session beans and MDBs are chained together.
In addition to an implementation of the standard JMS API, MockEJB also provides various convenience methods for sending messages and examining the content of queues without having to resort to the sometimes cumbersome JMS API. In this case, we haven't used these methods, since doing so has the disadvantage of preventing the tests being reused when running the EJBs inside a real EJB container, and the sample code supplied with this article demonstrates unit tests running in both modes.
To summarize, in a few short code fragments, we have managed to use MockEJB to deploy session and MDB beans to a local in-memory container, bind beans into the JNDI directory to allow them to call each other, and use MockEJB's in-memory JMS provider to test an MDB without needing to configure a real JMS provider in a container. Unfortunately, in a short article like this, we don't have space to show any more MockEJB features, but we hope that you can already see the potential of the framework for supporting efficient test-driven EJB development.
|
While we don't have space to demonstrate all the features of MockEJB, it is worthwhile to explore, briefly, the other facilities of this rich framework that we haven't discussed so far.
As well as providing facilities for testing session and message-driven beans, MockEJB provides facilities for testing BMP and CMP entity beans outside the container. The mock container provides an in-memory entity bean database, allowing this to be populated with test data to provide predictable testing without a database. BMP entity beans are treated similarly to session beans because of the bean implementation is present, while implementations are generated at runtime for CMP entity beans. MockEJB also provides its own aspect implementation (in the AOP sense of the term), which allows aspects to be installed to provide specific finder and container-managed relationship (CMR) behavior.
If cross-bean transactional behavior needs to be tested, MockEJB can be used in conjunction with MockRunner's MockUserTransaction implementation of the J2EE UserTransaction interface. An instance of a mock user transaction can be bound into MockEJB's mock JNDI directory, under the well-known name "javax.transaction.UserTransaction," and then this transaction object can be manipulated and monitored to check correct behavior. Similarly, MockRunner's JDBC implementation can be used within MockEJB to provide a mock datasource that can provide standard test data and that can be monitored for correct usage by the code under test.
As mentioned above, MockEJB provides a simple AOP framework that allows us to insert code into any point in the EJB processing life cycle. An aspect in MockEJB is defined as the combination of an interceptor that specifies the logic that will run before and after the target method is invoked (so called "around advice" in AOP parlance) and a pointcut that defines the set of methods to be intercepted. The implementation provided is powerful and flexible, allowing an interceptor to be introduced at the point of any method call in the framework (or bean or test code), allowing a test to be monitored (such as checking that a method is called) or data to be inserted (like returning a list of primary keys from a BMP finder method).
In fact, AOP presents a powerful alternative to the mock object approach since interceptors allow you to change the behavior of a single method of a target class as opposed to having to reimplement the entire class as a mock object. Both approaches can be used interchangeably with MockEJB; for example, you can create a mock version of the entire session bean (using the EasyMock framework, for instance) or intercept and alter just the selected methods of the bean.
Finally, a feature of MockEJB that we have mentioned, but not explained, is its ability to be used inside a regular EJB container. This use of MockEJB can make unit testing an EJB in the container much simpler, by providing you with an easily controlled runtime "wrapper" for your beans, therefore allowing the simulation of conditions that may be difficult to create in a routine unit test.
While running EJBs outside the container allows you to develop code using the TDD approach, it does not fully replace in-container testing. MockEJB does not support XML-based deployment descriptors (not to mention vendor-specific descriptors and configuration files), so deploying EJBs into the container and running unit tests is an important part of verifying their correctness. When using MockEJB, developers can rely on out-of-container mode for most of their development work, running in-container tests less frequently—for example before committing changes to the version control system.
To implement in-container support, MockEJB provides an extension to the Cactus ServletTestCase class, called OptionalCactusTestCase, which allows the tests in a test case subclass to be run on the server side, under Cactus, or outside the server using MockEJB's container. The behavior of the class is controlled by setting the system property mockejb.cactus.mode to true if the test is to be run under Cactus on the server. At runtime, tests can call the isRunningOnServer() method to vary their behavior between the two modes if required. In-container mode has the flexibility of mixing and matching container and mock-provided resources. This is accomplished by MockEJB's JNDI context delegating to the container's context when needed. As a result, you can rely on mock EJBs to control the boundaries of your test, whereas other resources (such as JMS and data sources) could be provided by the container to make your test more realistic.
The sample code that accompanies this article allows the unit tests supplied to be run against beans running inside or outside the EJB container, and therefore can be referred to as an example of using MockEJB inside the container.
MockEJbSamples.zip contains the example code for this article.
In this article, we have reviewed some of the problems of testing EJBs in a rapid develop/test/refactor cycle as suggested by the TDD approach, and introduced MockEJB as a testing framework that can help to resolve these problems by providing a lightweight mock EJB container that allows in-memory testing of EJBs.
As we have shown, with a relatively small number of lines of code (most of which can be placed in reusable base classes), it is possible to deploy a session bean or MDB to the mock container and test it locally, without needing to configure and use a full J2EE application server.
In addition, MockEJB provides a number of other useful facilities, including the ability to test entity beans, support for running inside the container once initial development is complete (allowing convenient control over a bean's runtime environment), support for JDBC and J2EE transactions via Mock Runner, and a flexible and powerful aspect-based interceptor implementation, allowing control and monitoring of the test environment.
We encourage you to try MockEJB on one of your projects and experience the streamlined unit test process it allows, particularly if you are attempting test-driven development with EJB components.
Eoin Woods has been working in the enterprise IT field for 15 years and is currently employed as an architect at a global investment bank, based in London. He has been working with BEA products since Tuxedo version 4.
Alexander Ananiev has over 16 years of experience in designing and developing computer systems using variety of languages and technologies. He currently works as an architect for a major consulting company.
Return to dev2dev.