2

How can I prevent Spring JmsTemplate unit test sendAndReceivePerson() from blocking when attempting to read a Person object from ActiveMQ queue person.queue?

The test creates a Person object, sends it to queue person.queue (this should also create the embedded broker that contains the queue), and then attempts to read the object from the same queue.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MessagingConfiguration.class})
public class PersonMessengerTest {
    @Autowired
    private PersonMessenger personMessenger;

    @Test
    public void sendAndReceivePerson() {
        final Person person = new Person();
        final UUID id = UUID.randomUUID();
        person.setId(id);
        person.setFirstName("Derek");
        person.setLastName("Mahar");
        personMessenger.sendPersion(person);
        final Person receivedPersion = personMessenger.receivePersion();
        assertEquals(id, receivedPersion.getId());
    }
}

public class PersonMessenger {

    private final JmsOperations jmsOperations;

    public PersonMessenger(JmsOperations jmsOperations) {
        this.jmsOperations = jmsOperations;
    }

    public void sendPersion(final Person person) {
        this.jmsOperations.convertAndSend(person);
    }

    public Person receivePersion() {
        return (Person) this.jmsOperations.receiveAndConvert();
    }
}

@Configuration
@Import({CommonConfiguration.class})
public class MessagingConfiguration {

    public static final String PERSON_QUEUE = "person.queue";

    @Autowired
    private ApplicationEnvironment applicationEnvironment;

    @Bean
    public ConnectionFactory jmsConnectionFactory() {
        final ActiveMQConnectionFactory activeMqConnectionFactory = new ActiveMQConnectionFactory(
                this.applicationEnvironment.getJmsBrokerUrl());
        activeMqConnectionFactory.setTrustAllPackages(true);
        return activeMqConnectionFactory;
    }

    @Bean
    public JmsOperations jmsTemplate(ConnectionFactory connectionFactory) {
        final JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
        jmsTemplate.setDefaultDestinationName(PERSON_QUEUE);
        return jmsTemplate;
    }

    @Bean
    public PersonMessenger personMessenger(JmsOperations jmsOperations) {
        return new PersonMessenger(jmsOperations);
    }

    @Bean(name = PERSON_QUEUE)
    public Queue personQueue() {
        return new ActiveMQQueue(PERSON_QUEUE);
    }
}

@Configuration
@PropertySource("classpath:application.properties")
public class CommonConfiguration {

    @Autowired
    private Environment applicationEnvironment;

    @Bean
    public ApplicationEnvironment applicationEnvironment() {
        return new ApplicationEnvironment(this.applicationEnvironment);
    }
}

application.properties:

jms.brokerUrl=vm://test?async=false&broker.persistent=false&broker.useJmx=false

Notice that the test blocks while attempting to read the Person object from the queue:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.opessoftware.example.spring.messenger.PersonMessengerTest
2017-02-15 15:30:33,736|WARN|main|o.a.activemq.broker.BrokerService|49670|
    org.apache.activemq.broker.BrokerService.checkMemorySystemUsageLimits(BrokerService.java:2142)
    Memory Usage for the Broker (1024mb) is more than the maximum available for the JVM: 955 mb - resetting to 70% of maximum available: 668 mb
2017-02-15 15:30:33,993|WARN|main|o.a.activemq.broker.BrokerService|49927|
    org.apache.activemq.broker.BrokerService.checkMemorySystemUsageLimits(BrokerService.java:2142)
    Memory Usage for the Broker (1024mb) is more than the maximum available for the JVM: 955 mb - resetting to 70% of maximum available: 668 mb

Might the receiveAndConvert() call be creating a second embedded broker or might it be reading from a different queue?

Derek Mahar
  • 27,608
  • 43
  • 124
  • 174

2 Answers2

2

A notorious problem when testing with an embedded broker.

The connection is closed between the operations and, without persistence, the data is lost.

Try wrapping the connection factory in a CachingConnectionFactory so the connection stays open (and hence the broker stays up).

You should really use a CachingConnectionFactory with a JmsTemplate anyway, for performance reasons, but it's particularly bad in a unit test.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • You are absolutely right! I stumbled across an example on Github that wrapped the `ConnectionFactory` in a `CachingConnectionFactory` and with this change, my test passed and output only a single `BrokerService` memory warning. Now to tackle that warning... – Derek Mahar Feb 16 '17 at 00:17
  • For what it's worth, https://github.com/lex-alpha7/bank_account/blob/master/jms/src/main/java/com/ostdlabs/bank_account/jms/config/JmsSpringConfig.java is the example that I had found which demonstrates how to wrap a `ConnectionFactory` in a `CachingConnectionFactory`. – Derek Mahar Feb 16 '17 at 16:22
1

As Gary Russell suggested, in order to prevent each JMS operation from closing the connection and discarding the embedded broker along with the Person message in the queue, method jmsConnectionFactory() in configuration class MessagingConfiguration now wraps the ActiveMQConnectionFactory (which implements ConnectionFactory) in a CachingConnectionFactory:

@Configuration
@Import({CommonConfiguration.class})
@EnableJms
public class MessagingConfiguration {

    public static final String PERSON_QUEUE = "person.queue";

    @Autowired
    private ApplicationEnvironment applicationEnvironment;

    @Bean
    public ConnectionFactory jmsConnectionFactory() {
        final ActiveMQConnectionFactory activeMqConnectionFactory = new ActiveMQConnectionFactory(
                this.applicationEnvironment.getJmsBrokerUrl());
        activeMqConnectionFactory.setTrustAllPackages(true);
        return new CachingConnectionFactory(activeMqConnectionFactory);
    }

    @Bean
    public JmsOperations jmsTemplate(ConnectionFactory connectionFactory) {
        final JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
        jmsTemplate.setDefaultDestinationName(PERSON_QUEUE);
        return jmsTemplate;
    }

    @Bean
    public PersonMessenger personMessenger(JmsOperations jmsOperations) {
        return new PersonMessenger(jmsOperations);
    }

    @Bean(name = PERSON_QUEUE)
    public Queue personQueue() {
        return new ActiveMQQueue(PERSON_QUEUE);
    }
}

Notice below that the test passes and outputs only a single BrokerService memory warning which implies that the test creates only a single embedded broker that all JMS operations use.

------------------------------------------------------------------------
Building spring-hibernate-jpa 1.0.0-SNAPSHOT
------------------------------------------------------------------------

--- maven-surefire-plugin:2.10:test (default-cli) @ spring-hibernate-jpa ---
Surefire report directory: /home/derek/Projects/spring-hibernate-jpa/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.opessoftware.example.spring.messenger.PersonMessengerTest
2017-02-16 10:14:03,743|WARN|main|o.a.activemq.broker.BrokerService|1579|
    org.apache.activemq.broker.BrokerService.checkMemorySystemUsageLimits(BrokerService.java:2142)
    Memory Usage for the Broker (1024mb) is more than the maximum available for the JVM: 955 mb - resetting to 70% of maximum available: 668 mb
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.929 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 4.190s
Finished at: Thu Feb 16 10:14:04 EST 2017
Final Memory: 8M/60M
------------------------------------------------------------------------
Community
  • 1
  • 1
Derek Mahar
  • 27,608
  • 43
  • 124
  • 174