如何在 Spring Boot 测试中模拟 Spring amqp/rabbit

Posted

技术标签:

【中文标题】如何在 Spring Boot 测试中模拟 Spring amqp/rabbit【英文标题】:how to mock spring amqp/rabbit in spring boot test 【发布时间】:2016-12-09 08:21:10 【问题描述】:

如何模拟 spring rabbitmq/amqp,使其在 Spring Boot 测试期间尝试自动创建交换/队列时不会失败?

鉴于我有一个简单的RabbitListener,它将导致队列和交换像这样自动创建:

@Component
@RabbitListener(bindings = 
        @QueueBinding(
                value = @Queue(value = "myqueue", autoDelete = "true"), 
                exchange = @Exchange(value = "myexchange", autoDelete = "true", type = "direct"), 
                key = "mykey")
)
@RabbitListenerCondition
public class EventHandler 
    @RabbitHandler
    public void onEvent(Event event) 
      ...
       

在一个简单的 Spring Boot 测试期间,像这样:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes =  Application.class )

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void test() 
        assertNotNull(applicationContext);
    


它会失败:

16:22:16.527 [SimpleAsyncTaskExecutor-1] ERROR o.s.a.r.l.SimpleMessageListenerContainer - Failed to check/redeclare auto-delete queue(s).
org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused
    at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:62)
    at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:309)

在这个测试中,我不关心 Rabbit/AMQP,那么我怎样才能模拟掉整个 Rabbit/AMQP?

【问题讨论】:

【参考方案1】:

不确定这是否有帮助,但我遇到了同样的问题。所以,我只是在 RabbitAdmin 上使用了 @MockBean 和不同的配置文件,并没有遇到相同的连接问题。测试通过。

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@RunWith(SpringRunner.class)
@ActiveProfiles("my-test")
public class ServiceTests 

@Autowired
private DummyService unitUnderTest;

@MockBean
private RabbitAdmin rabbitAdmin;

// lots of tests which do not need Spring to Create a RabbitAdmin Bean

【讨论】:

【参考方案2】:

我知道这是一个老话题,但我想介绍一个我正在开发的模拟库:rabbitmq-mock。

这个模拟的目的是在没有 IO(不启动服务器、监听某个端口等)和少量(~无)启动时间的情况下模拟 RabbitMQ 行为。

它在 Maven Central 中可用:

<dependency>
    <groupId>com.github.fridujo</groupId>
    <artifactId>rabbitmq-mock</artifactId>
    <version>1.1.1</version>
    <scope>test</scope>
</dependency>

基本用途是用测试覆盖 Spring 配置:

@Configuration
@Import(AmqpApplication.class)
class AmqpApplicationTestConfiguration 

    @Bean
    public ConnectionFactory connectionFactory() 
        return new CachingConnectionFactory(MockConnectionFactoryFactory.build());
    

要自动模拟 Spring bean 以进行测试,请查看我正在处理的另一个项目:spring-automocker

希望这能有所帮助!

【讨论】:

我试过了,一定是做错了什么。我得到:java.lang.ClassNotFoundException: com.rabbitmq.client.QueueingConsumer rabbitmq-mock 代码不使用这个com.rabbitmq.client.QueueingConsumer,你用的是什么版本的amqp-client 4.2.0.大约一年前,另一个开发人员实现了它。我只是想让测试运行而不会抛出异常。 @disco.dan.silver 你的依赖管理一定有问题,因为我只能找到 spring-rabbit (1.17.10.RELEASE) 指向 amqp-client (4.0.3) 和 spring-rabbit (2.0.0.RELEASE) 指向 amqp-client (5.0.0),没有提到 4.2.0 版本。除此之外,您链接的错误是由于缺少 deprecatedcom.rabbitmq.client.QueueingConsumer,它存在于 4.2.0 中,但在 5.0.0。您能否检查您的类路径中是否存在不需要的 ampq-client 依赖版本? 我能找到的唯一拥有 amqp-client 的是 org.springframework.boot:spring-boot-starter-amqp。【参考方案3】:

首先,在您的测试包中创建一个带有ConnectionFactory@Configuration

@Configuration
public class RabbitMqConfiguration 

    @Bean
    ConnectionFactory connectionFactory() 
        return new CachingConnectionFactory();
    

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) 
       return new RabbitTemplate(connectionFactory);
    


之后,从测试包中的 application.yml 中设置此属性:

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

这应该适用于 Spring Boot 2.2.x。

对于 Spring Boot 1.5.x,我还需要再添加一个依赖项:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-test-support</artifactId>
    <scope>test</scope>
</dependency>

我不知道为什么,但是没有 spring-cloud-stream-test-support 依赖项,我的集成测试尝试连接到 RabbitMQ 代理。即使不影响测试本身的结果,这在每次测试中都偷走了很多秒。我已经在another post 中看到了这种奇怪的行为。

【讨论】:

【参考方案4】:

有点类似于 Rajkishan's answer 对我不起作用:

这反而对我有用:

@SpringBootApplication
public class MyTestsApp 
    @Bean
    @Primary
    public CachingConnectionFactory rabbitAdmin() 
        return Mockito.mock(CachingConnectionFactory.class);
    


@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyTestsApp.class)
@ActiveProfiles(profiles = "test")
public class MyTests 


【讨论】:

【参考方案5】:

在我们的项目中,我们在本地使用docker 容器初始化RabbitMQ 实例。要运行集成测试,我们会在测试用例开始时启动 RabbitMQ 实例,并在清理期间将其关闭。

我们正在使用 TestContainers 来做到这一点。请参阅https://www.testcontainers.org/usage/dockerfile.html 和/或https://www.testcontainers.org/usage/docker_compose.html。

【讨论】:

【参考方案6】:

我在某个时候有类似的要求,并查看了 QPid,它提供了一个内存中的 AMQP 代理。它迫使您停留在 AMQP 级别,并尽可能少地使用 rabbitMq 特定代码。

但我实际上找到了另一种方法:通过在运行测试时调整队列和交换的名称 + 自动删除值,我们不再遇到问题了。测试中的所有队列/交换名称都以(运行测试的帐户的)用户名作为后缀,这意味着每个人都可以在他们的机器上运行测试而不会影响其他人。

即使在我们的 CI 管道中,多个项目也可能使用相同的交换/队列:我们将测试中的值配置为特定于项目,这样即使 2 个项目在同一台机器上同时运行它们的测试用户,消息不会在当前测试之外“泄漏”。

这最终比模拟或生成内存代理要简单得多。

【讨论】:

【参考方案7】:

这不是特别容易,如果代理不可用,我们通常使用 JUnit @Rule 跳过测试。

但是,我们确实有很多使用模拟的测试,但是您确实必须了解很多 Spring AMQP 内部结构才能使用它们。您可以在project itself 中探索测试用例。

有一次我确实尝试过编写一个模拟代理,但最终工作量太大。

【讨论】:

很奇怪,它曾经与 1.5.6 一起使用 - 我只需要像这样配置一个 Bean:Mockito.mock(AmqpTemplate.class) - 但现在使用 1.6.1 这不再适用:( 如果我是正确的,这也意味着当我有这样的配置时,我将无法使用任何带有完整容器的 SpringBoot 测试用例:( 模拟模板很容易;它涉及更多的模拟经纪人响应(确认,退货,交付)。解释“不再起作用”——AmqpTemplate 是一个简单的interface; 1.6 中没有任何东西会改变模拟它的能力。对于@RabbitListener,您必须模拟一个侦听器容器。 我不需要任何与 rabbit/amqp 相关的东西就可以在这个测试用例中工作,我想要的只是启动不会因为上面显示的错误而失败。 对于 1.5.6,我只需要模拟 AmqpTemplate,然后我就可以启动嵌入式 tomcat,而不会出现任何错误。

以上是关于如何在 Spring Boot 测试中模拟 Spring amqp/rabbit的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Boot 中模拟数据库连接以进行测试?

如何在 Spring-boot 中不模拟服务类的情况下为 REST 控制器端点编写单元测试

如何在 Spring Boot REST API 中为 db insert 方法编写模拟单元测试?

如何在 Java Spring boot 中模拟 RestTemplate?

如何模拟测试 Kotlin Spring Boot 2 应用程序

如何在 Spring Boot 测试中获取正在运行的服务器端口?