13

I want to run some acceptance tests for my services that are using rabbitMq but I want to ignore all that require inter-service communication (amqp).

The problem however is that Spring tries to connect to the (non-exisiting) rabbit host on startup so it can register its consumers. It does that for each method that is annotated with @RabbitListener which can get quite annoying with the long timeout this has if I have more than one listener in my service.

How can I reduce this timeout or even prevent @RabbitListener connection all together?

Our (simplified) Rabbit Config:

@Configuration
@EnableRabbit
public class RabbitMqConfig {

    public RabbitMqConfig(
            @Value("${rabbitmq.host}") String rabbitHost,
            @Value("${rabbitmq.port}") int rabbitPort,
            @Value("${exchange.name}") String exchange) {
        this.rabbitHost = rabbitHost;
        this.rabbitPort = rabbitPort;
        this.exchange= exchange;
    }

  @Bean
  DirectExchange directExchangeBean() {
    return new DirectExchange(this.exchange, true, false);
  }

  @Bean
  public ConnectionFactory connectionFactory() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitHost);
    connectionFactory.setPort(rabbitPort);
    return connectionFactory;
  }

  @Bean
  public RabbitTemplate rabbitTemplate() {
    return new RabbitTemplate(connectionFactory());
  }


  @Bean
  public Queue itemDoneQueue() {
    return new Queue(ITEM_DONE_QUEUENAME, true);
  }

  @Bean
  Binding itemDoneBinding() {
    return BindingBuilder.bind(itemDoneQueue()).to(directExchangeBean()).with(ITEM_DONE_KEY);
  }

}

Properties

rabbitmq.host=192.168.42.100
rabbitmq.port=5672
exchange.name=myExchange

The Listener:

  @RabbitListener(queues = ITEM_DONE_QUEUENAME)
  public void receiveMessageFromItemDoneQueue(String message) {
    // do the work
  }

The Test:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class}) 
public abstract class RabbitTest {

Really nothing special here. Obviously during testing the rabbit host is unavailable. That is fine. I want to ignore the fact. And quickly.

I've tried

spring.rabbitmq.connection-timeout=1

But that didn't change anything.

Using

spring.rabbitmq.listener.simple.auto-startup=false

neither does anything.

Using

spring.autoconfigure.exclude:org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration

just kills my application context loading with spring complaining about a NoSuchBeanDefinitionException: No bean named 'rabbitListenerContainerFactory' available

Any ideas? Thanks!

Vincent F
  • 6,523
  • 7
  • 37
  • 79
Pete
  • 10,720
  • 25
  • 94
  • 139

9 Answers9

7

I've had a similar problem, but solved it with

spring.rabbitmq.listener.direct.auto-startup=false

SpringBoot version 2.2.4.RELEASE

Spring framework version 5.2.3.RELEASE

sogu
  • 2,738
  • 5
  • 31
  • 90
4

I would expect the spring.rabbitmq.listener.simple.auto-startup=false to work - are you sure you're not trying to connect to Rabbit with some other code? Can you provide a DEBUG log to show the problem when that is set to false?

You can use the JUnit BrokerRunning @Rule to skip any tests that need a real RabbitMQ broker.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
4

If the property spring.rabbitmq.listener.simple.auto-startup=false does not have effect, you might be defining your own SimpleRabbitListenerContainerFactory bean

Check how this bean is defined in the RabbitAnnotationDrivenConfiguration.rabbitListenerContainerFactory()

The SimpleRabbitListenerContainerFactoryConfigurer binds together theSimpleRabbitListenerContainerFactory and properties defined in your application.properties (among other things)

If you use your own definition, then be sure to use something along the lines of

@Bean
SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
    SimpleRabbitListenerContainerFactoryConfigurer containerFactoryConfigurer, 
    ConnectionFactory connectionFactory) {

    SimpleRabbitListenerContainerFactory listenerContainerFactory =
            new SimpleRabbitListenerContainerFactory();
    containerFactoryConfigurer.configure(listenerContainerFactory, connectionFactory);

    return listenerContainerFactory;
}
LethalLima
  • 211
  • 3
  • 6
  • I have a custom SimpleRabbitListenerContainerFactory bean in my RabbitConfig configuration class and this worked as expected after I added the SimpleRabbitListenerContainerFactoryConfigurer and ConnectionFactory beans to the parameters to be passed in. I submitted an edit to your post to correct that. Thank you! – LethalLima Nov 21 '18 at 00:28
  • @LethalLima, accepted your edit as it clarifies usage of the provided code example for those who are not familiar with framework classes. I personally didn't go that way due to some peculiarities in my config – Volodymyr Belozorov Nov 22 '18 at 08:37
3

I've had a similar issue, but disabling the auto-startup did not solve all the issues

spring.rabbitmq.listener.direct.auto-startup=false

NOTE: depending on your type of connection you need to use either

listener.direct.auto-startup
or
listener.simple.auto-startup

In case you have actuator as dependency, it will automatically do a helthcheck and connection will be tried, even if the auto-startup is disabled. Adding also this, solved the issue for me

management.health.rabbit.enabled=false
andreyro
  • 935
  • 12
  • 18
2

If the property spring.rabbitmq.listener.simple.auto-startup=false does not have effect

You can try use container factory property:

 @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);

        factory.setAutoStartup(autoStartup); //autoStartup = false, in your case

        factory.setMessageConverter(jsonMessageConverter())
        return factory;
    }
Volodya Lombrozo
  • 2,325
  • 2
  • 16
  • 34
1

Thanks for your input Gary. I already found a solution that works for me. For some reason the connection timeout for the Consumer initialization is not exposed in the CachingConnectionFactory I used nor is the property used that I tried (spring.rabbitmq.connection-timeout).

What I do now is initialize the base ConnectionFactory and pass it to the CachingConnectionFactory (Having of course no clue where the difference is but that is the case most of the time with spring):

  @Bean
  public ConnectionFactory connectionFactory() {
    com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
    connectionFactory.setConnectionTimeout(this.connectionTimeout);
    connectionFactory.setHost(this.rabbitHost);
    connectionFactory.setPort(this.rabbitPort);
    CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(
        connectionFactory);
    return cachingConnectionFactory;
  }

Now I can set the timeout to 1 during my acceptence tests causing the consumer initialization to fail quickly and to a higher value for deployment.

Pete
  • 10,720
  • 25
  • 94
  • 139
  • How to do this with Rabbitmq 3.8.9 and Spring Boot 2.2.6.RELEASE? setConnectionTimeout method is not there in SimpleMessageListenerContainer class? – S Khandelwal Jun 14 '21 at 15:44
1

Instead of

spring.rabbitmq.listener.simple.auto-startup=false

I have used the below (with out the word 'simple' in the prop name) and it did work for me

spring.rabbitmq.listener.auto-startup=false

1

In my case

spring.rabbitmq.listener.simple.auto-startup: false

is ok but another manner also works

@EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class)
zhuguowei
  • 8,401
  • 16
  • 70
  • 106
0

You can mock the connection factory using mockito and add the @Rule of BrokerRunning.isNotRunning()

@Bean
    ConnectionFactory connectionFactory() {
        ConnectionFactory factory = mock(ConnectionFactory.class);
        Connection connection = mock(Connection.class);
        Channel channel = mock(Channel.class);
        willReturn(connection).given(factory).createConnection();
        willReturn(channel).given(connection).createChannel(anyBoolean());
        given(channel.isOpen()).willReturn(true);
        return factory;
    }

This is the rule that you will add to the test:

@Rule
public BrokerRunning brokerRunning = BrokerRunning.isNotRunning();
Atıfcan Ergin
  • 138
  • 1
  • 14