Thursday, April 1, 2010

Unit Test with a Reusable Spring-JMS Configuration

In my previous post, I introduced a general-purpose, allegedly reusable Spring configuration for an ActiveMQ-based JMS application. In this post, I'll expand on that by writing a unit test around a basic configuration that establishes an evolving prototype.

Writing tests that fail, as it turns out, is pretty simple. Let's pick that low-hanging fruit:

package com.mybiz.jms;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import javax.annotation.Resource;
import javax.jms.DeliveryMode;

@ContextConfiguration(locations =
{
    "classpath:com/mybiz/jms/spring-jms-demo.xml"}
)
public class BasicConfigTest extends AbstractTestNGSpringContextTests
{
    @Resource
    private JmsTemplate myTemplate;

    @BeforeClass
    public void setUp() throws Exception
    {
        System.out.println("Run BasicConfigTest...");
        assert applicationContext != null;
    }

    @Test
    public void testConfig() throws Exception
    {
        System.out.println("testConfig...");
        assert myTemplate.getDeliveryMode() == DeliveryMode.PERSISTENT;
        assert myTemplate.getTimeToLive() == 10000;
        assert myTemplate.isExplicitQosEnabled();
        assert myTemplate.isPubSubDomain();
        assert myTemplate.isSessionTransacted();
        assert myTemplate.isPubSubNoLocal();
    }
}

I don't need to tell you that, without any other work, this test will fail; let's take on something a bit less trivial. The test assumes a Spring configuration which resides in the same logical directory tree as the class. Here's that spring-jms-demo.xml configuration:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
       xmlns="http://www.springframework.org/schema/beans" 
       xsi:schemalocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
    <import resource="spring-context.xml"/>
    <import resource="spring-jms.xml"/>

</beans>

The first import establishes context-based annotationscanning for components and property placeholder
resolution, which will combine to help us reuse the generic Spring-JMS configuration (the second import). This spring-context.xml file is the beginning of my application-specific configuration:

<beans xmlns:context="http://www.springframework.org/schema/context"             
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns="http://www.springframework.org/schema/beans"  
       xsi:schemalocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
       
        <context:annotation-config>
            <context:component-scan base-package="com.mybiz.jms"/>
            <context:property-placeholder 
                location="com/mybiz/jms/spring-jms.properties" 
                system-properties-mode="OVERRIDE"/>
        </context:annotation-config>
</beans>

That file uses a properties file (spring-jms.properties) that backfills the generic Spring-JMS configuration with more of our application specifics:

# URL for connection to broker - use testing URL:
brokerURL=vm://localhost?async=false&broker.persistent=false
#
# messages will remain in broker for 10 seconds
timeToLive=10000
#
# 1 - non-persistent / 2 - persistent
deliveryMode=2
#
# Specify concurrency of 40 for starters
sessionCacheSize=40
#
# big perf boost, minimal risk (see comments in spring-jms.xml)
useAsyncSend=true
#
# support for flexibility on per-send basis
explicitQosEnabled=true
#
# concern is around a blocking consumer (see comments in spring-jms.xml)
dispatchAsync=true
#
# txn around sends
sessionTransacted=true
#
# just in case
pubSubNoLocal=true
#
# must specify this if it's a topic; default is false
pubSubDomain=true
#
# txn around receives
acknowledge=transacted
#
# destination for topic
destination=theTopic

Finally, I need two listeners that are referenced by the Spring-JMS configuration:

package com.mybiz.jms;

import org.springframework.stereotype.Service;

@Service
public class MyListener {}  // works for now; but this will need some work


package com.mybiz.jms;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;

@Component
public class MyExceptionListener implements ExceptionListener
{
    private final Logger log = LoggerFactory.getLogger(getClass());
    public void onException(JMSException e)
    {
        log.error("JMS error", e);
    }
}

Now the unit test will run successfully; there's no (meaningful) output to show you, and for that matter the test and listeners are along the lines of "what's the simplest thing that would work" - it's really only done to demonstrate that all the moving parts are in place and that I've used them correctly so far. In my next, I'll start adding on some target functionality and look for ways to test it.

No comments:

Post a Comment