7

I have written a RabbitMQ consumer by implementing the MessageListener interface and setting up a SimpleMessageListenerContainer. Its working well when I test it manually. Now I would like to write an integration test that:

  1. Creates a message
  2. Pushes the message to my RabbitMQ server
  3. Waits while the message is consumed by my MessageListener implementation
  4. Test makes some asserts once everything is done

However, since my MessageListener is running in a separate thread, it makes unit testing difficult. Using a Thread.sleep in my test to wait for the MessageListener is unreliable, I need some kind of blocking approach.

Is setting up a response queue and using rabbitTemplate.convertSendAndReceive my only option? I wanted to avoid setting up response queues, since they won't be used in the real system.

Is there any way to accomplish this using only rabbitTemplate.convertAndSend and then somehow waiting for my MessageListener to receive the message and process it? Ideally, I would imagine something like this:

rabbitTemplate.convertAndSend("routing.key", testObject);
waitForListner() // Somehow wait for my MessageListener consume the message
assertTrue(...)
assertTrue(...)

I know I could just pass a message directly to my MessageListener without connecting to RabbitMQ at all, but I was hoping to test the whole system if it is possible to do so. I plan on falling back to that solution if there is no way to accomlish my goal in a reasonably clean way.

Ry Lowry
  • 173
  • 1
  • 7

2 Answers2

4

There are several approaches, the easiest being to wrap your listener and pass in a CountDownLatch which is counted down by the listener and the main test thread uses

assertTrue(latch.await(TimeUnit.SECONDS));

You can also pass back the actual message received so you can verify it is as expected.

Also see the integration test cases in the framework itself.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • This approach worked well for me and was pretty unobtrusive. Some of the explanations from this post http://stackoverflow.com/questions/17827022/what-is-countdown-latch-in-java-multithreading were also helpful. – Ry Lowry Jan 22 '15 at 23:59
  • 2
    @GaryRussell What if you have an existing Listener class and you want to avoid adding a `CountDownLatch` only for testing? Is there a workaround for integration testing in that case? – Glide Jan 12 '16 at 02:18
  • You can add an advice to the listener container; see [the discussion in this question/answer](http://stackoverflow.com/questions/34454159/how-to-write-an-integration-test-for-rabbitlistener-annotation/34454922#34454922) and a [test case I added to the framework](https://github.com/spring-projects/spring-amqp/pull/353). These are centered around `@RabbitListener` but the same techniques apply. I have some [work in process here](https://github.com/garyrussell/spring-amqp/commit/045b7494b250a41061dab02c845db3a2c3bccb0e) to formalize a mechanism to add such advices for test case scenarios. – Gary Russell Jan 12 '16 at 02:33
  • Also [see this answer](http://stackoverflow.com/questions/34513662/how-do-we-hook-into-before-after-message-processing-using-rabbitlistener/34514526#34514526). However, as I said in the answer above, you can simply wrap your listener and put the latch in the wrapper without changing your listener object. – Gary Russell Jan 12 '16 at 02:35
  • I dont understand the integration test cases for the framework itself, and SingleConnectionFactory is not even available at this moment, is it something Im doing it wrong? or the integration test are not for being used outside the framework? – jpganz18 Jul 25 '16 at 18:50
  • You can use the `CachingConnectionFactory` for your tests; that connection factory is just for the framework test cases - but we should probably get rid of it; it adds no value. – Gary Russell Jul 25 '16 at 18:52
  • Thanks for the reply @GaryRussell (you are a master!!) I was just wondering, can I also replace the BrokerRunning class as well? seems like it is for the internal use of the framework too, is there any alike class? – jpganz18 Jul 25 '16 at 18:58
  • Yeah - it would be nice if we could have moved it to `spring-rabbit-test` but we couldn't do that because we'd end up with a circular dependency between `spring-rabbit` and `spring-rabbit-test`. We'd have to move it to a completely different project that both those depend on. For now, the only work-around is to copy the class to your project. – Gary Russell Jul 25 '16 at 19:04
0

We can wait for the message to be consumed by relying on checking the queue is empty. RabbitAdmin can help us to check the queue message count and with the help of java dependency from http://www.awaitility.org/ we can able to achieve it.

rabbitTemplate.convertAndSend("routing.key", testObject);
awaitForQueueToBeConsumed("routing.key") 
assertTrue(...)
assertTrue(...)

void awaitForQueueToBeConsumed(final String queue) {
  await().atMost(1, TimeUnit.SECONDS).until(isQueueEmpty(queue));
}

Callable<Boolean> isQueueEmpty(final String queue) {
  return () -> rabbitAdmin.getQueueInfo(queue).getMessageCount() == 0;
}
Prasanna Anbazhagan
  • 1,693
  • 1
  • 20
  • 37