2
votes

I wonder if anyone can help. My starter for 10 is that I know very little (next to nothing) about JMS and messaging in general - so please go easy with me with any answers/comments :)

Given that this is a learning exerise for me, I'm trying to put together a very basic Spring JMS config, and then write some integration tests to help me understand how it all works.

This is my current Spring context config with its JMS components:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/jms
                           http://www.springframework.org/schema/jms/spring-jms.xsd
                           http://activemq.apache.org/schema/core
                           http://activemq.apache.org/schema/core/activemq-core.xsd">

    <bean class="com.lv.gi.jmstest.ApplicationContextProvider" />

    <!--  Embedded ActiveMQ Broker -->
    <amq:broker id="broker" useJmx="false" persistent="false">
        <amq:transportConnectors>
            <amq:transportConnector uri="tcp://localhost:0" />
        </amq:transportConnectors>
    </amq:broker>

    <!--  ActiveMQ Destination  -->
    <amq:queue id="destination" physicalName="myQueueName" />

    <!-- JMS ConnectionFactory to use, configuring the embedded broker using XML -->
    <amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost" />

    <!-- JMS Producer Configuration -->
    <bean id="jmsProducerConnectionFactory"
          class="org.springframework.jms.connection.SingleConnectionFactory"
          depends-on="broker"
          p:targetConnectionFactory-ref="jmsFactory" />

    <bean id="jmsProducerTemplate" class="org.springframework.jms.core.JmsTemplate"
          p:connectionFactory-ref="jmsProducerConnectionFactory"
          p:defaultDestination-ref="destination" />

    <bean class="com.lv.gi.jmstest.JmsMessageProducer">
        <constructor-arg index="0" ref="jmsProducerTemplate" />
    </bean>

    <!-- JMS Consumer Configuration -->
    <bean id="jmsConsumerConnectionFactory"
          class="org.springframework.jms.connection.SingleConnectionFactory"
          depends-on="broker"
          p:targetConnectionFactory-ref="jmsFactory" />

    <jms:listener-container container-type="default"
                            connection-factory="jmsConsumerConnectionFactory"
                            acknowledge="auto">
        <jms:listener destination="myQueueName" ref="jmsMessageListener" />
    </jms:listener-container>

    <bean id="jmsMessageListener" class="com.lv.gi.jmstest.JmsMessageListener" />
</beans>

My JmsMessageProducer class has a postMessage method as follows:

public void postMessage(final String message) {
    template.send(new MessageCreator() {
        @Override
        public Message createMessage(final Session session) throws JMSException {
            final TextMessage textMessage = session.createTextMessage(message);
            LOGGER.info("Sending message: " + message);

            return textMessage;
        }
    });
}

And my JmsMessageListener (implements MessageListener) has an onMessage method as follows:

public void onMessage(final Message message) {
    try {
        if (message instanceof TextMessage) {
            final TextMessage tm = (TextMessage)message;
            final String msg = tm.getText();

            LOGGER.info("Received message '{}'", msg);
        }
    } catch (final JMSException e) {
        LOGGER.error(e.getMessage(), e);
    }
}

In my test class, I can fire up the Spring context, get the JmsMessageProducer bean, and call postMessage; and I see the message on the console as expected:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/com/lv/gi/jmstest/JmsMessageListenerTest-context.xml" })
public class TestJms {

    private JmsMessageProducer jmsMessageProducer;

    @Before
    public void setup() {
        jmsMessageProducer = ApplicationContextProvider.getApplicationContext().getBean(JmsMessageProducer.class);
    }

    @Test
    public void doStuff() throws InterruptedException {
        jmsMessageProducer.postMessage("message 1");
    }
}

Whilst this works, its not really much of a test, because other than me visually seeing the message received on the console, I can't assert the message has been received.

We use mockito, so I'm wondering if there is a way within my test that I can replace the MessageListener bean with a mock, then call verify on it. I guess I could do it by providing a different Spring context file for the purpose of these tests, but that might not work well with my next requirement ...

My end goal with all of this is to create a Topic, where my message producer can fire a message onto the queue, and 1 of more MessageListeners will read the message off the queue, and when all registered listeners have read the message, the message is deleted from the queue. (I think Topic is the right terminology for this!)

To prove that this system would work, I would want a test where I can fire up the Spring context. The first thing I'd want to do is replace the listener with 3 mocks all wired to the same destination so that I can use verify on each of them. I'd post a message, then verify that each mock has received. Then I'd want to remove/disable 2 of the listeners, call postMessage, and verify the onMessage method was called on the one remaining mock listener. Then perhaps wait a while, and re-establish the 2 mocks, and verify their onMessage method was called. And finally, check the message is no longer on the queue (by virtue of the fact that the 3 registered listeners had all received the message)

With the above in mind, I think what I'm trying to do is register and de-register (or disable) listeners against the destination at runtime, and if I can do that, then I can register mocks.

Phew!, that's complicated!, but I hope it makes sense as to what I'm trying to do?

Any pointers as to how to achieve this? Many thanks in advance,

Nathan

1
OK, this post tells me how to start and stop listeners: stackoverflow.com/questions/11407838/… Just need to work out how to register listeners now (so I can register my mocks)Nathan Russell

1 Answers

1
votes

In my opinion, as soon as you are doing integration testing, you should not try to mock anything.

On one hand you write unit tests. For example, you could test the behaviours of your consumer, by calling the onMessage method of jmsMessageListener directly from your tests. You typically don't use SpringJUnit4ClassRunner for this kind of test, and you typically use Mockito to mock the dependencies of the object you are testing.

On the other hand you have integration tests. These are testing the behaviour of your entire application. It would make sense to use SpringJUnit4ClassRunner.class here, but not Mockito. You should test that whatever your jmsListener was supposed to do has been done. For example, if your application was supposed to write logs about the incoming message, open the log file and check it.

Your example here is very simple, maybe this is why you are confused (in my opinion). With a more complex listener, it would be more natural to it isolated from the rest of the system.