Search code examples
spring-bootspring-integrationintegration-testingspring-testactivemq-artemis

Multiple test application contexts listening on competing consumer queue cause intermittent test failures


I have a JMSInboundGateway that I am testing which listens to an Apache Artemis queue (competing consumer). My test sends a message to the Artemis server and mocks the destination service. If the mock service is called then I have verified that the JmsInboundGateway is set up appropriately.

The flow looks like this: Test Sender -> Artemis Queue -> JmsInboundGateway -> DirectChannel -> ServiceActivator -> Mock(Destination Service)

The test runs like a champ if it is the only test class running in the JUnit test suite; however, if there are other test classes in the suite then the test fails. I've tracked down that when the test fails there are up to three consumers for the Artemis queue: I presume two are for ApplicationContexts that do not have the Mocked service bean, and another for the context that has the mocked bean. Whether the test passes or fails depends on whether the proper context receives the message.

One thing I have tried that seems to work is to optionally register the JmsInboundGateway when a specific profile is active and only activate that profile on the messaging test (and the live app of course).

// Declaring my inbound gateways with the profile requirement
@Profile("messaging")
@Bean
public JmsInboundGateway jmsInboundGateway(ConnectionFactory connFactory) { ... }

// Running my tests with
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("messaging")
public class MessagingTest { ... }

This causes the message thieving context to never listen for messages on the queue and allows the proper context to be the exclusive listener. This is not a particularly satisfying solution since I am likely to have multiple test classes that will require the "messaging" profile and I have verified that they will step on one another's toes.

If I add the @DirtiesContext annotation to each test marked with @ActiveProfiles("messaging") then this does appear to solve the problem when there are multiple messaging tests. I only observe one consumer on the Artemis queue during test suite execution and I can have multiple test classes with messaging enabled.

// The following appears to permit me to have multiple messaging enabled tests
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("messaging")
@DirtiesContext
public class MessagingTest { ... }

This also seems clunky to me but it's my best solution thus far. Are there any testing helpers and/or patterns out there that I am missing that can help me to get around this problem?

Many thanks!


Solution

  • With the @DirtiesContext you really go right way.

    The problem that you share a JMS resource in between the application contexts in your tests classes. That's how you really clash to one or another existing consumer, just because the whole application context for another class is cached during execution and it gets access to the JMS as well as your current context.

    This is really was the way we went in Spring Integration several years ago to solve similar problem for our JMS and JDBC tests. We also asked Spring Test Framework developers to do this as a default functionality, but that sounded as a big breaking change and unreasonable for typical unit tests which don't share resources.

    Since there we go a direction to always think if our tests starts some background threads or gets access to a shared resource, e.g. embedded MongoDB, Hazelcast or just some directory on the file system. So, in those cases we definitely use that @DirtiesContext and pretty happy with passed tests.