如何在 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 版本。除此之外,您链接的错误是由于缺少 deprecated 类 com.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?