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
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
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.