How to Prevent Message Stealing in SpringBoot Integration Tests with RabbitMQ Testcontainer? - spring-boot

Imagine the following setup:
A springBoot Applicaiton with a RabbitListener to a queue
Two SpringBoot integration tests with RabbitMQ testcontainer
Test A does some arbitrary test cases which require RabbitMQ
Test B has a slightly different test configuration and sends a message from the test code to the application via RabbitMQ and expects something to happen.
Because of the different configuration, Test A will have a different (and cached) spring context than Test B.
When Test A runs, a Rabbit MQ consumer is started. After the test, the Spring context is cached and the rabbit consumer is still running.
When then Test B runs, a second Rabbit MQ consumer is started in the second spring context. When now the test sends a message, sometimes the consumer from context A "steals" the message and thus the test B fails.
I could only "fix" that but running all integration tests with RabbitMQ with #DirtiesContext but that make the test suite very very slow.
Is there any other thing that can be done to prevent that? Is there some kind of spring context "pause" that can be called to stop the consumers temporarily until they are resumed when the context is reused?

Related

How to disconnect from rabbitmq after each test?

I have integrated with rabbitmq in a spring application. There are two SpringRunner tests which assert on whether the amqp receiver receives a message. The tests connect to a rabbitmq broker running in a separate process.
The problem is that the application context loads on first test and registers a consumer to the queue, but does not disconnect after the test completes.
When the second test runs, the application context for it also registers a consumer, but any messages sent to the exchange as a part of the second test still go to the consumer registered by application context from the first test.
Both the tests run sequentially.
Is there a way to kill the first context completely before the second test starts so that there is just one consumer at a time ? Or any other way to solve the problem ?
Thank you
Tried #DirtiesContext before test did not help
Well, to be honest a #DirtiesContext on all the test classes level, alongside with the #RunWith(SpringRunner.class), is the way to.
The ListenerContainer is an active component which starts its own threads, so even when you are done with your test, it doesn't mean that background thread is stopped. For this purpose you indeed have to use a #DirtiesContext on every test class to ensure that all the application contexts are closed after finishing tests. It ensures that those listener containers are stopped as well.
It is just not enough to place a #DirtiesContext on your one test class, because there is no guarantee in which order they are going to be called. So, present it as much as possible on your test classes to avoid this or similar race conditions.

Managing JMS Message Containers on Application Startup and Shutdown

Currently, we have four JMS listener containers that are started during the application start. They all connect through Apache ZooKeeper and are manually started. This becomes problematic when a connection to ZooKeeper cannot be established. The (Wicket) application cannot start, even though it is not necessary for the JMS listeners be active to use the application. They simply need to listen to messages in the background, save them and a cron job will process them in batches.
Goals:
Allow the application to start and not be prevented by the message containers not being able to connect.
After the application starts, start the message listeners.
If the connection to one or any of the message listeners goes down, it should attempt to automatically reconnect.
On application shutdown (such as the Tomcat being shutdown), the application should stop the message listeners and the cron job that processes the saved messages.
Make all of this testable (as in, be able to write integration tests for this setup).
Current Setup:
Spring Boot 1.5.6
Apache ZooKeeper 3.4.6
Apache ActiveMQ 5.7
Wicket 7.7.0
Work done so far:
Define a class that implements ApplicationListener<ApplicationReadyEvent>.
Setting the autoStart property of the DefaultMessageListenerContainer to false and start each container in the onApplicationEvent in a separate thread.
Questions:
Is it necessary to start each message container in its own thread? This seems to be overkill, but the way the "start" process works is that the DefaultMessageListenerContainer is built for that listener and then it is started. There is a UI component that a user can use to start/stop the message listeners if need be, and if these are started sequentially in one thread, then the latter three message containers could be null if the first one has yet to connect on startup.
How do I accomplish goals 4 and 5?
Of course, any commments on whether I am on the right track would be helpful.
If you do not start them in a custom thread then the whole application cannot be fully started. It is not just Wicket, but the Servlet container won't change the application state from STARTING to STARTED due to the blocking request to ZooKeeper.
Another option is to use a non-blocking request to ZooKeeper but this is done by the JMS client (ActiveMQ), so you need to check whether this is supported in their docs (both ActiveMQ and ZooKeeper). I haven't used those in several years, so I cannot help you more.

Spring Cloud Contract and plain Spring AMQP

We are using plain Spring AMQP in our spring boot projects.
We want to make sure that our message consumers can test against real messages and avoid to test against static test messages.
Thus our producers could generate message snippets in a test phase that can be picked up by the consumer test to make sure it tests against the latest message version and see if changes in the producer break the consumer.
It seems like Spring Cloud Contract does exactly that. So is there a way to integrate spring cloud contract with spring amqp? Any hints in which direction to go would be highly appreciated.
Actually we don't support it out of the box but you can set it up yourself. In the autogenerated tests we're using an interface to receive and send messages so
you could implement your own class that uses spring-amqp. The same goes for the consumer side (the stub runner). What you would need to do is to implement and register a bean of
org.springframework.cloud.contract.verifier.messaging.MessageVerifier type for both producer and consumer. This should work cause what we're doing in the autogenerated tests is that we #Inject MessageVerifier
so if you register your own bean it will work.
UPDATE:
As #Mathias has mentioned it, the AMQP support is already there in Spring Cloud Contract https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_stub_runner_spring_amqp

JUnit and jms - events fired are not consumed in the middle of test

We are using spring with junit , jms (activemq) and mySql.
We would like to create some junit tests that after their executions the db will rollback.
In order to achieve that we are using the #Transactional annotation for each tests.
Problem is, one of our tests is calling a service that sends a jms message, (in the middle of the test) the thing is the event is being consumed only after the tests ends (end of transaction maybe?)
thats why the assertion in the end of the test fails.
any ideas why the event is not being consumed right away (p.s we tried to use sleep in order to let the event be consumed, its not working)
Firstly, this is not a unit test ... that's fine ... There are 2 reasons message is not consumed:
The transaction in which the message is sent is not committed. Due to this the lock on the message on the server side is not released.
There is a timing issue and the test ends too soon and doesn't wait got the callback.
That's the whole point of transactions. The message is not available for consumption until the transaction commits (otherwise how could you roll it back if someone's already seen it?). You can do the send in a new transaction (Propagation.REQUIRES_NEW) if you don't want the send to be part of the encompassing transaction.

Spring JMS injection causing application not to startup

We have a spring application that publishes and listens to queues on a remote application server. My publisher and listener which are spring based listen within our own application server.
One of the problems we have for our test environments is the other app. server is not up so when our test application goes to start and it tries to inject JmsTemplate with its connectionFactory it blows up because it is not a valid connection and our entire application fails to load. This is causing grief with the other developers in our group that have nothing to do with JMS. All they want to do is run and test their code but the JmsTemplate connectionFactory is down.
Does anyone have any suggestion for enabling spring ignore some bad injections which will allow our application to start properly?
Thanks
I believe this could be achieved by defining separate spring profiles and then passing it as a parameter in your test environments while starting your application. You could mock or ignore any beans then.
Example
import org.springframework.context.annotation.Profile;
#Profile("test")
public class AppConfigTest {
....
....
}
JVM param / System property
-Dspring.profiles.active=test

Resources