0

I have integration test with docker using test containers. On container I run jms. In test I am putting message on queue.

How I can wait in test to make it populated on jms?

On local machine it works, but on jenkins it fails, so I have to add

    Thread.sleep(3000);

but this is nasty. org.awaitility seems to be missed usage:

await().atMost(2, TimeUnit.SECONDS).until(() -> return true));

I just need to do a pause to make jms propagate (put on jms queue) and wait for listener to act, which is putting message to database. Then I have to call get rest endpoint to see it worked.

With topic it would be easier, because I would create test listener on topic. But it is queue, there can be on listener that will get message.

TylerH
  • 20,799
  • 66
  • 75
  • 101
masterdany88
  • 5,041
  • 11
  • 58
  • 132
  • Not sure why you call sleep nasty, it's a workaround but it works... Is there no way to check if jms queue contains a message in your await().asMost() call? – Vitaly Chura Apr 09 '21 at 08:24
  • Using `sleep()` is not ideal because it's brittle and inefficient. It's brittle because if the machine slows down even more then the sleep duration still won't be sufficient. It's inefficient because if the machine speeds up it will be sleeping for no reason. This can really add up over lots of tests. – Justin Bertram Apr 09 '21 at 13:31
  • do you populate the message with your Testcontainers container definition or inside the code? Testcontainers provides several wait strategies: https://www.testcontainers.org/features/startup_and_waits/ in case you're sending the message as part of the container setup – rieckpil Apr 12 '21 at 05:41

3 Answers3

1

Use org.awaitility with a JMS QueueBrowser, e.g.:

@Test
public void myTest() throws Exception {
   ...
   await().atMost(2, TimeUnit.SECONDS).until(() -> return queueIsEmpty(queueName)));
   ...
}

private boolean queueIsEmpty(String queueName) {
   ConnectionFactory cf = new MyBrokersConnectionFactory();
   Connection connection = cf.createConnection();
   Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   QueueBrowser browser = session.createBrowser(session.createQueue(queueName));
   Enumeration enumeration = senderBrowser.getEnumeration();
   while (enumeration.hasMoreElements()) {
      return false;
   }
   return true;
}

A QueueBrowser is read only so there is no danger that it will actually consume the message.

Another potential option would be to create a consumer with a transacted session and then try to receive the message. If you actually did receive a message you could rollback the transaction and close the consumer.

Justin Bertram
  • 29,372
  • 4
  • 21
  • 43
0

Use retries (e.g. Spring RetryTemplate or Failsafe Retry Policy) to improve integration test execution time:

  • Retry the SQL query until record is present
  • Retry the REST endpoint until it is successful

Here an example to wait for a DB record; tweak the policies to your needs:

RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setBackOffPolicy(new FixedBackOffPolicy());
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(
         10, Collections.singletonMap(AssertionError.class, true)));
retryTemplate.execute(retryContext -> {
    List<MyRecord> records = jdbcTemplate.query("select ...");
    Assert.assertEquals(1, records.size());
    return null;
});
Daniel Steinmann
  • 2,119
  • 2
  • 15
  • 25
-1

My solution is to use org.awaitility lib and replace asserts with return statement:

await().atMost(30, TimeUnit.SECONDS).until(
    () -> {
        //
        // assertTrue(condition);
        return condition == true;
    }
Adriaan
  • 17,741
  • 7
  • 42
  • 75
masterdany88
  • 5,041
  • 11
  • 58
  • 132