如何在 spring 启动测试中模拟 spring amqp/rabbit
how to mock spring amqp/rabbit in spring boot test
如何模拟 spring rabbitmq/amqp 以便在尝试自动创建 exchanges/queues 时在 Spring 引导测试期间不会失败?
鉴于我有一个简单的 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 启动测试中,像这样:
@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都mock掉呢?
这不是特别容易,如果代理不可用,我们通常使用 JUnit @Rule
跳过测试。
但是,我们确实有很多使用模拟的测试,但您确实必须了解很多 Spring AMQP 内部结构才能使用它们。您可以在 project itself.
中探索测试用例
有一次我确实尝试编写一个模拟代理,但最终工作量太大了。
我在某个时候有类似的要求,并查看了 QPid,它提供了一个内存中的 AMQP 代理。它迫使您停留在 AMQP 级别,并尽可能少地使用 rabbitMq 特定代码。
但我实际上找到了另一种方法:通过在 运行ning 测试 + auto-delete 值时调整队列和交换的名称,我们不再有问题了。测试中的所有 queue/exchange 名称都带有用户名(帐户 运行ning 测试)的后缀,这意味着每个人都可以 运行 在他们的机器上进行测试而不影响其他人。
即使在我们的 CI 管道中,多个项目也可能使用相同的 exchanges/queues :我们将测试中的值配置为特定于项目的,因此即使 2 个项目 运行 他们的同一用户在同一台机器上同时测试,消息不会 "leak" 当前测试之外。
与模拟或生成内存代理相比,这最终更易于管理。
在我们的项目中,我们在本地使用 docker
容器初始化一个 RabbitMQ
实例。对于 运行 集成测试,我们会在测试用例的开头启动一个 RabbitMQ
实例,并在清理期间将其关闭。
我们正在使用 TestContainers 来做到这一点。请参阅 https://www.testcontainers.org/usage/dockerfile.html and/or https://www.testcontainers.org/usage/docker_compose.html。
不确定这是否有帮助,但我遇到了同样的问题。所以,我只是在 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
}
首先,在您的测试包中创建一个 @Configuration
和 ConnectionFactory
:
@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 代理。即使不影响测试本身的结果,这也会在每次测试中偷走很多秒。我已经在 .
中看到了这种奇怪的行为
我知道这是一个老话题,但我想介绍一下我正在开发的模拟库:rabbitmq-mock。
这个 mock 的目的是模仿没有 IO(不启动服务器、监听某些端口等)和较小的 (~ none) 启动时间的 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
希望对您有所帮助!
有点类似于 但对我不起作用:
这对我有用:
@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 {
}
如何模拟 spring rabbitmq/amqp 以便在尝试自动创建 exchanges/queues 时在 Spring 引导测试期间不会失败?
鉴于我有一个简单的 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 启动测试中,像这样:
@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都mock掉呢?
这不是特别容易,如果代理不可用,我们通常使用 JUnit @Rule
跳过测试。
但是,我们确实有很多使用模拟的测试,但您确实必须了解很多 Spring AMQP 内部结构才能使用它们。您可以在 project itself.
中探索测试用例有一次我确实尝试编写一个模拟代理,但最终工作量太大了。
我在某个时候有类似的要求,并查看了 QPid,它提供了一个内存中的 AMQP 代理。它迫使您停留在 AMQP 级别,并尽可能少地使用 rabbitMq 特定代码。
但我实际上找到了另一种方法:通过在 运行ning 测试 + auto-delete 值时调整队列和交换的名称,我们不再有问题了。测试中的所有 queue/exchange 名称都带有用户名(帐户 运行ning 测试)的后缀,这意味着每个人都可以 运行 在他们的机器上进行测试而不影响其他人。
即使在我们的 CI 管道中,多个项目也可能使用相同的 exchanges/queues :我们将测试中的值配置为特定于项目的,因此即使 2 个项目 运行 他们的同一用户在同一台机器上同时测试,消息不会 "leak" 当前测试之外。
与模拟或生成内存代理相比,这最终更易于管理。
在我们的项目中,我们在本地使用 docker
容器初始化一个 RabbitMQ
实例。对于 运行 集成测试,我们会在测试用例的开头启动一个 RabbitMQ
实例,并在清理期间将其关闭。
我们正在使用 TestContainers 来做到这一点。请参阅 https://www.testcontainers.org/usage/dockerfile.html and/or https://www.testcontainers.org/usage/docker_compose.html。
不确定这是否有帮助,但我遇到了同样的问题。所以,我只是在 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
}
首先,在您的测试包中创建一个 @Configuration
和 ConnectionFactory
:
@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 代理。即使不影响测试本身的结果,这也会在每次测试中偷走很多秒。我已经在
我知道这是一个老话题,但我想介绍一下我正在开发的模拟库:rabbitmq-mock。
这个 mock 的目的是模仿没有 IO(不启动服务器、监听某些端口等)和较小的 (~ none) 启动时间的 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
希望对您有所帮助!
有点类似于
这对我有用:
@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 {
}