Test a JMS message consumer

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

Problem

You would like to test a JMS message consumer without running the messaging server.

Background

You can overcome one of the major difficulties in testing a message-driven bean by testing it in isolation as a JMS message consumer. Simply instantiate the message lis- tener directly, and use a mock objects approach to substitute Test Objects in place of the message consumer’s collaborators. Even with this simplification, there are some issues with message consumers that you need to be aware of during testing:

427 Test a JMS message consumer

without the messaging server

Communication via JMS is still entirely asynchronous—This means that, for example, a message consumer cannot throw an exception and expect the message producer to receive it. Error handling is trickier with JMS message consumers, so it is important to focus more energy on ensuring that the message producers never send invalid messages.

JMS message consumers are typically deployed in a J2EE application as message-driven beans—Although it is not strictly necessary to do so, wrapping your message consumer in a message-driven bean helps you leverage the EJB container’s services, such as participating in transactions, guaranteed delivery, and so on. Even if you are building a standalone JMS message consumer class, the odds are good that you will (eventually) wrap it in a message-driven bean.

Testing a JMS message consumer carries with it essentially the same issues as test- ing a message-driven bean, so it is no accident that this recipe resembles the mes- sage-driven bean recipes in this chapter.

Recipe

Most message-driven beans are 95% JMS message consumer and 5% EJB. Perhaps the most EJB-like thing your message-driven beans do is implement ejbCreate() to lookup collaborators in a JNDI directory and cache them. Aside from that, though, you can test a message-driven bean and a JMS message consumer in essen- tially the same way. For that reason, the tests in this recipe are similar in approach to the ones in recipe 11.9, “Test a legacy message-driven bean.”

In recipe 11.8 we described an overall design for a message-driven bean. It describes a kind of Decorator-like approach, starting with message-processing logic, wrapping that in a JMS message consumer, and then finally wrapping that in a message-driven bean (see figure 11.2 for the relevant sequence diagram). You can apply that design pattern here, ignoring the last layer of wrapping. This allows you to test the message-receiving logic—interaction with the JMS API—without having to rely on the correctness of the business logic that responds to the mes- sage. Returning to our order-processing example, our JMS message consumer, ProcessOrderSubmissionMessageListener, retrieves a collaborator from the JNDI directory, unmarshals the message, and then executes a ProcessOrderSubmission- Action. Because we instantiate the message listener in our test, we can substitute a Spy version of the action. This allows us to verify that the message consumer invokes the action with the parameters it received from the message. Listing 11.14 shows the relevant test.28

28

428 CHAPTER 11

Testing Enterprise JavaBeans

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

import javax.jms.MapMessage;

import javax.naming.InitialContext;

import

junit.cookbook.coffee.model.jms.ProcessOrderSubmissionMessageListener;

import junit.cookbook.coffee.model.logic.ProcessOrderSubmissionAction;

import junit.cookbook.coffee.service.MailService;

import junit.framework.TestCase;

import org.mockejb.jndi.MockContextFactory;

import com.sun.jms.MapMessageImpl;

public class ProcessOrderSubmissionMessageListenerTest extends TestCase

implements MailService { private boolean invoked;

protected void setUp() throws Exception { invoked = false;

MockContextFactory.setAsInitial();

new InitialContext().bind("java:comp/env/service/Mail", this);

}

public void testHappyPath() throws Exception { ProcessOrderSubmissionAction spyAction = new ProcessOrderSubmissionAction() { public void processOrder(

MailService mailService, String customerEmailAddress) { invoked = true;

assertEquals(

"jbr@diasparsoftware.com", customerEmailAddress);

} };

ProcessOrderSubmissionMessageListener consumer =

new ProcessOrderSubmissionMessageListener(spyAction);

MapMessage message = new MapMessageImpl();

message.setString(

ProcessOrderSubmissionMessageListener .CUSTOMER_EMAIL_PARAMETER_NAME, "jbr@diasparsoftware.com");

consumer.onMessage(message);

assertTrue("Did not invoke the processing action.", invoked);

}

public void sendMessage(

Listing 11.14 ProcessOrderSubmissionMessageListenerTest

429 Test a JMS message consumer

without the messaging server

String fromAddress, String toAddress, String subject, String bodyText) {

fail("No-one should ever invoke me.");

} }

The test creates a Spy version of the “process submitted order” action, passes it to the JMS message consumer, simulates sending a message, and then verifies that the message consumer invoked the action with the correct customer e-mail address. We decided to use the Self-Shunt pattern to implement MailService, because we find the resulting test to be a little easier to read. The alternative is to create a separate DoNotUseMeMailService that simply throws an exception when- ever some tries to invoke it (a crash test dummy). We have the MailService behave this way to emphasize the point that we are overriding the message-process- ing logic and therefore do not expect to actually try to use the MailService passed into it. If it did, then our test would not be testing what we think it would be test- ing, and we would want to know that so we can correct it.

To substitute our Spy version of the message-processing logic we used the tech- nique we described in recipe 2.11, “Test an object that instantiates other objects.”

We could have extracted an interface [Refactoring, 341] around the action, but given how simple the class is, we felt it was sufficient to merely subclass it and over- ride its only method.

We have verified that our message consumer sends the correct parameters to the message-processing logic. We still need to test that logic itself, which we describe in recipe 11.11, “Test JMS message-processing logic.”

Discussion

The test in this recipe is very similar to the one we wrote in recipe 11.9, except that we substitute a Spy version of the message-processing logic, rather than a Spy version of the MailService. The distinction is subtle, but important. This test does not rely on the correctness of the business logic that the application wants to invoke in response to the message, whereas our legacy message-driven bean test does. This is common in testing legacy code: it is typically more tightly coupled to its environment, making it more difficult to write the kind of focused, isolated test we generally prefer to write. Part of this is a perception problem: project manag- ers, architects, and technical leads often see EJBs as complicated components that

430 CHAPTER 11

Testing Enterprise JavaBeans

are best left alone as much as possible. We have observed a kind of psychological barrier to refactoring EJBs that we do not see as strongly when we suggest refactor- ing other kinds of components. Even though message-driven beans are quite sim- ilar to their JMS message consumer counterparts, it is common to think of message-driven beans as more complex, simply because they are EJBs. Whatever the reason, we find it easier to convince others to let us refactor a JMS message consumer than a message-driven bean. This allows us to write simpler tests, such as the one in this recipe, which verifies that the message consumer specifies the correct e-mail address for processing. This is simpler than the legacy message- driven test, which verifies that an e-mail was sent to the correct e-mail address. The test in this recipe is more focused and less easily perturbed by, say, a failure in the JNDI directory or a temporary problem with the mail server. Your deployment tests can check the contents of the JNDI directory (see recipe 11.13).

Related

■ 2.11—Test an object that instantiates other objects

■ 11.9—Test a legacy message-driven bean

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

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

(753 trang)