Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until the end of this week:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Overview

In this tutorial, we’ll create a simple Spring application that connects to ActiveMQ to send and receive messages. We’ll focus on testing this application and the different approaches to test Spring JMS overall.

2. Application Setup

First, let's create a basic application that can be used for testing. We'll need to add the necessary dependencies and implement the message handling.

2.1. Dependencies

Let’s add the required dependencies to our project’s pom.xml. We need Spring JMS to be able to listen to JMS messages. We’ll use ActiveMQ-Junit to start an embedded ActiveMQ instance for a part of the tests and TestContainers to run an ActiveMQ Docker container in the other tests:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jms</artifactId>
    <version>4.3.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.apache.activemq.tooling</groupId>
    <artifactId>activemq-junit</artifactId>
    <version>5.16.5</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.17.3</version>
    <scope>test</scope>
</dependency>

2.2. Application Code

Now let’s create a Spring application that can listen for messages:

@ComponentScan
public class JmsApplication {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(JmsApplication.class);
    }
}

We need to create a configuration class and enable JMS with the @EnableJms annotation and configure the ConnectionFactory to connect to our ActiveMQ instance:

@Configuration
@EnableJms
public class JmsConfig {

    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        return factory;
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory("tcp://localhost:61616");
    }

    @Bean
    public JmsTemplate jmsTemplate() {
        return new JmsTemplate(connectionFactory());
    }
}

After this, let’s create our listener that can receive and process messages:

@Component
public class MessageListener {

    private static final Logger logger = LoggerFactory.getLogger(MessageListener.class);

    @JmsListener(destination = "queue-1")
    public void sampleJmsListenerMethod(TextMessage message) throws JMSException {
        logger.info("JMS listener received text message: {}", message.getText());
    }
}

We’ll also need a class that can send messages:

@Component
public class MessageSender {

    @Autowired
    private JmsTemplate jmsTemplate;

    private static final Logger logger = LoggerFactory.getLogger(MessageSender.class);

    public void sendTextMessage(String destination, String message) {
        logger.info("Sending message to {} destination with text {}", destination, message);
        jmsTemplate.send(destination, s -> s.createTextMessage(message));
    }
}

3. Testing With Embedded ActiveMQ

Let’s test our application. We’ll use an embedded ActiveMQ instance first. Let’s create our test class and add a JUnit Rule that manages our ActiveMQ instance:

@RunWith(SpringRunner.class)
public class EmbeddedActiveMqTests4 {

    @ClassRule
    public static EmbeddedActiveMQBroker embeddedBroker = new EmbeddedActiveMQBroker();

    @Test
    public void test() {
    }

    // ...
}

Let’s run this empty test and inspect the logs. We can see that an embedded broker is started with our test:

INFO | Starting embedded ActiveMQ broker: embedded-broker
INFO | Using Persistence Adapter: MemoryPersistenceAdapter
INFO | Apache ActiveMQ 5.14.1 (embedded-broker, ID:DESKTOP-52539-254421135-0:1) is starting
INFO | Apache ActiveMQ 5.14.1 (embedded-broker, ID:DESKTOP-52539-254421135-0:1) started
INFO | For help or more information please see: http://activemq.apache.org
INFO | Connector vm://embedded-broker started
INFO | Successfully connected to vm://embedded-broker?create=false

The broker is stopped after all the tests are executed in our test class.

We need to configure our application to connect to this ActiveMQ instance so that we can test our MessageListener and MessageSender classes properly:

@Configuration
@EnableJms
static class TestConfiguration {
    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        return factory;
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        return new ActiveMQConnectionFactory(embeddedBroker.getVmURL());
    }

    @Bean
    public JmsTemplate jmsTemplate() {
        return new JmsTemplate(connectionFactory());
    }
}

This class uses a special ConnectionFactory which gets the URL from our embedded broker. Now we need to use this configuration by adding the @ContextConfiguration annotation to the class containing our tests:

@ContextConfiguration(classes = { TestConfiguration.class, MessageSender.class }) public class EmbeddedActiveMqTests {

3.1. Sending Messages

Let’s write our first test and check the functionality of our MessageSender class. First, we need to get a reference to an instance of this class by simply injecting it as a field:

@Autowired
private MessageSender messageSender;

Let’s send a simple message to ActiveMQ and add some assertions to check the functionality:

@Test
public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException {
    String queueName = "queue-2";
    String messageText = "Test message";

    messageSender.sendTextMessage(queueName, messageText);

    assertEquals(1, embeddedBroker.getMessageCount(queueName));
    TextMessage sentMessage = embeddedBroker.peekTextMessage(queueName);
    assertEquals(messageText, sentMessage.getText());
}

Now we are sure that our MessageSender works properly because the queue contains exactly one entry with the correct text after we send the message.

3.2. Receiving Messages

Let’s check our listener class as well. Let’s start with creating a new test method and sending a message with the embedded broker. Our listener is set to use “queue-1” as its destination, so we need to ensure that we are using the same name here.

Let’s use Mockito to check the listener's behavior. We’ll use the @SpyBean annotation to get an instance of the MessageListener:

@SpyBean
private MessageListener messageListener;

Then, we'll check if the method was called and capture the received method argument with an ArgumentCaptor:

@Test
public void whenListening_thenReceivingCorrectMessage() throws JMSException {
    String queueName = "queue-1";
    String messageText = "Test message";

    embeddedBroker.pushMessage(queueName, messageText);
    assertEquals(1, embeddedBroker.getMessageCount(queueName));

    ArgumentCaptor<TextMessage> messageCaptor = ArgumentCaptor.forClass(TextMessage.class);

    Mockito.verify(messageListener, Mockito.timeout(100)).sampleJmsListenerMethod(messageCaptor.capture());

    TextMessage receivedMessage = messageCaptor.getValue();
    assertEquals(messageText, receivedMessage.getText());
}

We can now run the tests, and both of them pass.

4. Testing With TestContainers

Let’s see another approach for testing JMS in Spring applications. We can use TestContainers to run an ActiveMQ Docker container and connect to it in our tests.

Let’s create a new test class and include the Docker container as a JUnit Rule:

@RunWith(SpringRunner.class)
public class TestContainersActiveMqTests {

    @ClassRule
    public static GenericContainer<?> activeMqContainer 
      = new GenericContainer<>(DockerImageName.parse("rmohr/activemq:5.14.3")).withExposedPorts(61616);

    @Test
    public void test() throws JMSException {
    }
}

Let’s run this test and check the logs. We can see some information related to TestContainers as it is pulling the specified docker image and as it starts the container:

INFO | Creating container for image: rmohr/activemq:5.14.3
INFO | Container rmohr/activemq:5.14.3 is starting: e9b0ddcd45c54fc9994aff99d734d84b5fae14b55fdc70887c4a2c2309b229a7
INFO | Container rmohr/activemq:5.14.3 started in PT2.635S

Let’s create a configuration class similar to what we implemented with ActiveMQ. The only difference is the configuration of the ConnectionFactory:

@Bean
public ConnectionFactory connectionFactory() {
    String brokerUrlFormat = "tcp://%s:%d";
    String brokerUrl = String.format(brokerUrlFormat, activeMqContainer.getHost(), activeMqContainer.getFirstMappedPort());
    return new ActiveMQConnectionFactory(brokerUrl);
}

4.1. Sending Messages

Let’s test our MessageSender class and see if it works with this Docker container. This time we can’t use the methods on the EmbeddedBroker, but the Spring JmsTemplate is easy to use too:

@Autowired
private MessageSender messageSender;

@Autowired
private JmsTemplate jmsTemplate;

@Test
public void whenSendingMessage_thenCorrectQueueAndMessageText() throws JMSException {
    String queueName = "queue-2";
    String messageText = "Test message";

    messageSender.sendTextMessage(queueName, messageText);

    Message sentMessage = jmsTemplate.receive(queueName);
    Assertions.assertThat(sentMessage).isInstanceOf(TextMessage.class);

    assertEquals(messageText, ((TextMessage) sentMessage).getText());
}

We can use the JmsTemplate to read the contents of the queue and check if our class sent the correct message.

4.2. Receiving Messages

Testing our listener class isn’t much different either. Let’s send a message using the JmsTemplate and verify if our listener received the correct text:

@SpyBean
private MessageListener messageListener;

@Test
public void whenListening_thenReceivingCorrectMessage() throws JMSException {
    String queueName = "queue-1";
    String messageText = "Test message";

    jmsTemplate.send(queueName, s -> s.createTextMessage(messageText));

    ArgumentCaptor<TextMessage> messageCaptor = ArgumentCaptor.forClass(TextMessage.class);

    Mockito.verify(messageListener, Mockito.timeout(100)).sampleJmsListenerMethod(messageCaptor.capture());

    TextMessage receivedMessage = messageCaptor.getValue();
    assertEquals(messageText, receivedMessage.getText());
}

5. Conclusion

In this article, we created a basic application that can send and receive messages with Spring JMS. Then, we discussed two ways to test it.

Firstly, we used an embedded ActiveMQ instance that even provides some convenient methods to interact with the broker. Secondly, we used TestContainers to test our code with a docker container that simulates real-world scenarios better.

As always, the source code for these examples is available over on GitHub.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until the end of this week:

>> GET ACCESS NOW

Junit footer banner
Comments are closed on this article!