Writing Unit Tests to test JMS Queue listener code with ActiveMQ

During my GSoC project for OpenNMS i did some work with JMS and wanted to write test classes to make sure that my code was working well. In this article i will try to explain how you can use ActiveMQ to write test cases for your JMS code. This is very handy when your code has a part that listens to a JMS queue.

Apache ActiveMQ 

Apache ActiveMQ is a extremely popular and very powerful open source messaging and Integration Patterns server. You can check it out here. The part we will be using to run our test classes is the embedded broker that is provided by ActiveMQ. This allows you to create a temporary broker for test purposes to create a JMS queue in our case. If you want to learn more about the embedded broker check out this article. The easiet way to create a embedded broker is through the following code line. This will automatically create a embedded broker.
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");

Code to be tested

Before we take a look at the test code lets take a look at the code that we are trying to test. here the purpose of code is to listen to a given JMS Queue and do some processing on the received data. to allow smooth operation of the listener code we will need to create 3 classes.

The Server class this is the main class that will listen to the Queue. below is the code snippet that will start the JMS Queue connection.
public void startServer(){
        try{
            ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");

            // create a queue connection
            QueueConnection queueConn = (QueueConnection) connectionFactory.createConnection();

            // create a queue session
            QueueSession queueSession = queueConn.createQueueSession(false,
                                                                     Session.DUPS_OK_ACKNOWLEDGE);

            Destination destination = queueSession.createQueue("TESTQUEUE");


            // create a queue receiver
            MessageConsumer consumer = queueSession.createConsumer(destination);

            // set an asynchronous message listener
            MeasurementListner measurementListner = new MeasurementListner();
            consumer.setMessageListener(measurementListner);

            // set an asynchronous exception listener on the connection
            MeasurementExceptionListener measurementExceptionListener = new MeasurementExceptionListener();
            queueConn.setExceptionListener(measurementExceptionListener);
            queueConn.start();

        }catch(Exception e){
            logger.error("JMS Exception",e.getMessage());
        }
To make sure that the server is running continually you can use a wait method as given below and call it just after calling the startServer() method. If you only want to run the server for a limited time you can use a Thread.sleep(timeinmillies).
 synchronized void waitForever()
    {   
        while (true) {
            try {
                wait();
            } catch (InterruptedException ex) { }


        }
    }
The other 2 classes that you will need is a class that implements the MessageListener interface and ExceptionListener interface the two classes MeasurementListner and MeasurementExceptionListener are those two classes, the onMessage(Message message) method is called when a message is received from the JMS Queue. So we want to test if the code actually gets messages from the defined queue that why we need the ActiveMQ embedded broker.

Writing the Test code
 public class ListnerTest {

    private String jmsQueue = "TESTQUEUE";
    private String url = "vm://localhost?broker.persistent=false";
    @Test
    public void testMeasurment(){

        try {
            .........
            String string = new String("Hello");
           
            sendMessage(string);

            Server server = new Server();
            server.main(null);
            ........
            
        } catch (MessageConversionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 

    }
   

    private void sendMessage(Object obj){
        try{
            
            SimpleMessageConverter smConverter = new SimpleMessageConverter();
            ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
            Message message;

            
            QueueConnection queueConn = (QueueConnection) connectionFactory.createConnection();
            queueConn.start();

            QueueSession queueSession = queueConn.createQueueSession(false,
                                                                     Session.DUPS_OK_ACKNOWLEDGE);

            Destination destination = queueSession.createQueue(jmsQueue);

            MessageProducer queueSender = queueSession.createProducer(destination);
            queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
            
            message = smConverter.toMessage(obj, queueSession);
            queueSender.send(message);
            
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

}

The test code simple creates a String and writes into the JMS Queue created using the embedded broker of ActiveMQ. i am calling the main method of the server since the startServer() call and wait call is made in the main method. If you got everything running you should be able to get a nice unit test running for your project. I hope this was helpful to anyone trying to do this. please leave a comment if you need any clarification i can help out if possible.


Comments

  1. You have no assertions in your test case this is pretty pointless

    ReplyDelete
    Replies
    1. Hi

      Thanks for the feedback, hope my comment below gives you some explanation :)

      Delete
  2. I agree with the previous commenter. Probably thousands of people are having a look at the article. Make sure it is of the highest quality. e.g. spend time on your test assertions and exception handling. For your tests, you probably don't want to log and use a RuntimeException instead.
    I needed something similar. JUnit now offers the concept of rules. It would be nice to have a rule that automatically starts a broker if you annotate the test class or test method with @BrokerService or something.

    This is what I did:

    Broker broker = testClass.getAnnotation(Broker.class);
    if (broker != null)
    {
    brokerService = new BrokerService();
    brokerService.setBrokerName("dev");
    brokerService.setPersistent(false);
    brokerService.setUseJmx(true);
    brokerService.addConnector("tcp://localhost:61616");
    brokerService.start();
    }

    Anyway, good initiative and keep up the posting :-)

    ReplyDelete
  3. Hi

    Well i didn't put the assertion part into the blog post since my objective was to explain how to use ActiveMQ embedded server in the test code.I have assertion test in my original code. my bad on that i will make sure to make my next posts more complete :). thanks for the feedback.

    ReplyDelete
  4. I think that you don´t really need the Server class at all, an embedded broker is created automatically (and before that line is executed) when an ActiveMqConnectionFactory is created with the "vm://" transport inside sendMessage().

    Anyway, in general I'd prefer to have two methods to start and stop a BrokerService as the second commenter pointed out, annotated with either @Before and @After or @BeforeClass and @AfterClass. In that way, JUnit will handle that for you and the execution will not get stuck in the main method of Server.

    Also, in my opinion, any test involving JMS is not complete unless both the sender and the receiver are set up. And in general, unit tests should *NOT* catch Exceptions, those are errors and by catching them you are hiding them from the JUnit run so that they will not appear in the report, just in the console output.

    Finally, I'd consider using some other transport (e.g. "tcp://") in the unit test if it is going to be used in the application. When using "vm://" ActiveMq doesn't serialize the message but just passes on the object reference, so if something is wrong in that area you might not catch it in the unit test.

    ReplyDelete
    Replies
    1. Hi Enrique

      Thank you for your insight, yes the line that creates the ActiveMqConnectionFactory creates the embedded server, most of the code segments i used here are a small part of a larger project that i worked on so i think i should have done a better explanation on the usage of the server. That project was written to work as a executable jar that can be deployed anywhere and it will listen to a JMS queue for data and store them in a Cassandra database.So that is where the server part comes in.

      I think i can use your advice on using @Before and @After annotations to improve my original code.

      I will try to improve the quality of my posts in the future with all the feedback :). im also pretty new to JMS so really appreciate your feedback.

      Delete

Post a Comment

Popular posts from this blog

Reading and Writing Binary files in java - Converting Endianness ( between Big-endian byte order and Little-endian byte order)

How to set up a Apache Spark cluster in your local machine