|
该版本仍在开发中,尚未被视为稳定。请使用最新的稳定版本,使用 Spring AMQP 4.0.0! |
测试支持
为异步应用编写集成必然比测试更简单的应用更复杂。
当抽象如@RabbitListener注释也加入了。
问题在于如何验证发送消息后,监听者是否如预期般收到了消息。
该框架本身包含许多单元测试和集成测试。 有些人使用模拟测试,另一些则使用实时的 RabbitMQ 代理进行集成测试。 你可以参考这些测试来获取一些测试场景的想法。
春季AMQP 1.6版引入了春兔测试jar,它支持测试一些更复杂的场景。
预计该项目将随着时间扩展,但我们需要社区反馈,提出有助于测试所需的功能建议。
请使用JIRA或GitHub Issues来提供此类反馈。
@SpringRabbitTest
使用此注释将基础设施豆添加到春季测试中应用上下文.
例如,在使用时则不需要这样做@SpringBootTest因为Spring Boot的自动配置会添加豆子。
注册的豆子有:
-
缓存连接工厂(自动连接工厂).如果@RabbitEnabled存在,其连接工厂被使用。 -
兔子模板(autoRabbit模板) -
兔子管理员(自动兔子管理员) -
兔子听众容器工厂(autoContainerFactory)
此外,相关Beans@EnableRabbit(支持@RabbitListener)被添加。
@SpringJUnitConfig
@SpringRabbitTest
public class MyRabbitTests {
@Autowired
private RabbitTemplate template;
@Autowired
private RabbitAdmin admin;
@Autowired
private RabbitListenerEndpointRegistry registry;
@Test
void test() {
...
}
@Configuration
public static class Config {
...
}
}
用JUnit4替换@SpringJUnitConfig跟@RunWith(SpringRunnner.class).
木斗回答吗<>实现
目前有两个回答吗<>帮助测试的实现。
第一,锁定倒计时和呼叫真实方法答案,提供答案<虚无>回归零倒数一个门闩。
以下示例展示了如何使用锁定倒计时和呼叫真实方法答案:
LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
.when(listener).foo(anyString(), anyString());
...
assertThat(answer.await(10)).isTrue();
第二条,LambdaAnswer<T>提供了一种可选调用实方法的机制,并提供了机会
返回基于模仿召唤以及结果(如有)。
请考虑以下POJO:
public class Thing {
public String thing(String thing) {
return thing.toUpperCase();
}
}
以下类测试东西POJO:
Thing thing = spy(new Thing());
doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + r))
.when(thing).thing(anyString());
assertEquals("THINGTHING", thing.thing("thing"));
doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + i.getArguments()[0]))
.when(thing).thing(anyString());
assertEquals("THINGthing", thing.thing("thing"));
doAnswer(new LambdaAnswer<String>(false, (i, r) ->
"" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString());
assertEquals("thingthing", thing.thing("thing"));
从2.2.3版本开始,答案会捕捉被测方法抛出的任何异常。
用answer.getExceptions()为了获得对他们的参考。
当与@RabbitListenerTest和RabbitListenerTestHarness用harness.getLambdaAnswerFor(“listenerId”,true, ...)为了让听众得到一个结构合理的答案。
@RabbitListenerTest和RabbitListenerTestHarness
注释你的其中一个@Configuration类别@RabbitListenerTest使框架取代
标准兔子听众注释豆后处理器其中有一个子类称为RabbitListenerTestHarness(它也使得@RabbitListener通过@EnableRabbit).
这RabbitListenerTestHarness它通过两种方式增强听众的体验。
首先,它将听者包裹在莫奇托间谍,使得正常木斗截止和验证作。
它还可以添加一个建议对监听者开放,从而能够访问参数、结果以及抛出的任何异常。
你可以通过属性控制哪些(或两个)这些设置被启用,@RabbitListenerTest.
后者用于访问关于调用的低层数据。
它还支持在调用异步监听器之前阻止测试线程。
最后 @RabbitListener方法不能被监视或建议。
此外,只有拥有身份证属性可以被监视或建议。 |
举几个例子。
以下示例使用间谍:
@Configuration
@RabbitListenerTest
public class Config {
@Bean
public Listener listener() {
return new Listener();
}
...
}
public class Listener {
@RabbitListener(id="foo", queues="#{queue1.name}")
public String foo(String foo) {
return foo.toUpperCase();
}
@RabbitListener(id="bar", queues="#{queue2.name}")
public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
...
}
}
public class MyTests {
@Autowired
private RabbitListenerTestHarness harness; (1)
@Test
public void testTwoWay() throws Exception {
assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));
Listener listener = this.harness.getSpy("foo"); (2)
assertNotNull(listener);
verify(listener).foo("foo");
}
@Test
public void testOneWay() throws Exception {
Listener listener = this.harness.getSpy("bar");
assertNotNull(listener);
LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("bar", 2); (3)
doAnswer(answer).when(listener).foo(anyString(), anyString()); (4)
this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
assertTrue(answer.await(10));
verify(listener).foo("bar", this.queue2.getName());
verify(listener).foo("baz", this.queue2.getName());
}
}
| 1 | 把安全带注入测试箱,这样我们才能接触到间谍。 |
| 2 | 拿到间谍的参考,这样我们才能核实它是否按预期被调用。
由于这是一个发送和接收作,无需暂停测试线程,因为它已经是
悬挂在兔子模板等待回复。 |
| 3 | 在这种情况下,我们只使用发送作,因此需要一个锁存来等待异步调用监听器
在容器线程上。
我们使用Answer<?>实现来帮助实现这一点。
重要提示:由于听者被监视的方式,使用该工具非常重要harness.getLatchAnswerFor()为了给间谍一个配置合适的答案。 |
| 4 | 配置间谍以调用答. |
以下示例使用捕获建议:
@Configuration
@ComponentScan
@RabbitListenerTest(spy = false, capture = true)
public class Config {
}
@Service
public class Listener {
private boolean failed;
@RabbitListener(id="foo", queues="#{queue1.name}")
public String foo(String foo) {
return foo.toUpperCase();
}
@RabbitListener(id="bar", queues="#{queue2.name}")
public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
if (!failed && foo.equals("ex")) {
failed = true;
throw new RuntimeException(foo);
}
failed = false;
}
}
public class MyTests {
@Autowired
private RabbitListenerTestHarness harness; (1)
@Test
public void testTwoWay() throws Exception {
assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));
InvocationData invocationData =
this.harness.getNextInvocationDataFor("foo", 0, TimeUnit.SECONDS); (2)
assertThat(invocationData.getArguments()[0], equalTo("foo")); (3)
assertThat((String) invocationData.getResult(), equalTo("FOO"));
}
@Test
public void testOneWay() throws Exception {
this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
this.rabbitTemplate.convertAndSend(this.queue2.getName(), "ex");
InvocationData invocationData =
this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS); (4)
Object[] args = invocationData.getArguments();
assertThat((String) args[0], equalTo("bar"));
assertThat((String) args[1], equalTo(queue2.getName()));
invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
args = invocationData.getArguments();
assertThat((String) args[0], equalTo("baz"));
invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
args = invocationData.getArguments();
assertThat((String) args[0], equalTo("ex"));
assertEquals("ex", invocationData.getThrowable().getMessage()); (5)
}
}
| 1 | 把安全带注入测试箱,这样我们才能接触到间谍。 |
| 2 | 用harness.getNextInvocationDataFor()以获取调用数据——这里是请求/回复
这种情况下无需等待任何时间,因为测试线程在兔子模板等待
为了结果。 |
| 3 | 这样我们可以验证论证和结果是否如预期。 |
| 4 | 这次我们需要一些时间等待数据,因为这是容器线程的异步作,我们需要 以暂停测试线程。 |
| 5 | 当监听者抛出异常时,该异常可以在可投掷调用数据的属性。 |
使用自定义时回答吗<>为了正常运作,这类答案应被子类前言召唤并且从安全带上获取实际的监听者(不是间谍)getDelegate(“myListener”))并呼叫super.answer(祈祷).
参见提供的木斗回答吗<>实现示例的源代码。 |
用测试兔子模板
这测试兔子模板提供用于在不依赖代理的情况下进行一些基础的集成测试。
当你把它添加为@Bean在你的测试用例中,它会发现上下文中的所有监听器容器,无论是否声明为@Bean或<豆/>或使用以下@RabbitListener注解。
目前它只支持按队列名称进行路由。
模板从容器中提取消息监听器,并直接在测试线程中调用。
请求-回复消息(发送与接收方法)支持回复的听众。
以下测试用例使用该模板:
@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {
@Autowired
private TestRabbitTemplate template;
@Autowired
private Config config;
@Test
public void testSimpleSends() {
this.template.convertAndSend("foo", "hello1");
assertThat(this.config.fooIn, equalTo("foo:hello1"));
this.template.convertAndSend("bar", "hello2");
assertThat(this.config.barIn, equalTo("bar:hello2"));
assertThat(this.config.smlc1In, equalTo("smlc1:"));
this.template.convertAndSend("foo", "hello3");
assertThat(this.config.fooIn, equalTo("foo:hello1"));
this.template.convertAndSend("bar", "hello4");
assertThat(this.config.barIn, equalTo("bar:hello2"));
assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4"));
this.template.setBroadcast(true);
this.template.convertAndSend("foo", "hello5");
assertThat(this.config.fooIn, equalTo("foo:hello1foo:hello5"));
this.template.convertAndSend("bar", "hello6");
assertThat(this.config.barIn, equalTo("bar:hello2bar:hello6"));
assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4hello5hello6"));
}
@Test
public void testSendAndReceive() {
assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
}
@Configuration
@EnableRabbit
public static class Config {
public String fooIn = "";
public String barIn = "";
public String smlc1In = "smlc1:";
@Bean
public TestRabbitTemplate template() throws IOException {
return new TestRabbitTemplate(connectionFactory());
}
@Bean
public ConnectionFactory connectionFactory() throws IOException {
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;
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() throws IOException {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
return factory;
}
@RabbitListener(queues = "foo")
public void foo(String in) {
this.fooIn += "foo:" + in;
}
@RabbitListener(queues = "bar")
public void bar(String in) {
this.barIn += "bar:" + in;
}
@RabbitListener(queues = "baz")
public String baz(String in) {
return "baz:" + in;
}
@Bean
public SimpleMessageListenerContainer smlc1() throws IOException {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
container.setQueueNames("foo", "bar");
container.setMessageListener(new MessageListenerAdapter(new Object() {
public void handleMessage(String in) {
smlc1In += in;
}
}));
return container;
}
}
}
JUnit4@Rules
Spring AMQP 1.7及以后版本提供了一个额外的jar,称为春兔朱尼特.
这个罐子里有几个实用的东西@Rule用于运行JUnit4测试时的实例。
请参见JUnit5条件以进行JUnit5测试。
用经纪运行
经纪运行提供了一种机制,使得在代理未运行时(在 上)测试成功本地主持,默认情况下)。
它还具备初始化和清空队列、删除队列和交换的工具方法。
以下示例展示了其用途:
@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");
@AfterClass
public static void tearDown() {
brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}
有好几个是奔跑......静态方法,例如isBrokerAndManagementRunning(),该插件验证经纪人是否启用了管理插件。
规则配置
有时如果没有代理,测试会失败,比如夜间CI构建。要在运行时禁用该规则,设置一个名为RABBITMQ_SERVER_REQUIRED自true.
你可以用设置变量或环境变量覆盖代理属性,比如主机名:
以下示例展示了如何用设定器覆盖属性:
@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");
static {
brokerRunning.setHostName("10.0.0.1")
}
@AfterClass
public static void tearDown() {
brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}
你也可以通过设置以下环境变量来覆盖属性:
public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI";
public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME";
public static final String BROKER_PORT = "RABBITMQ_TEST_PORT";
public static final String BROKER_USER = "RABBITMQ_TEST_USER";
public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD";
public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER";
public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD";
这些环境变量覆盖默认设置(本地主持人:5672对于AMQP和localhost:15672/API/用于管理 REST API)。
更改主机名称会影响AMQP和管理REST API 连接(除非管理员 URI 被明确设置)。
经纪运行还提供静态的方法setEnvironmentVariableOverrides这样你就可以传递包含这些变量的映射。它们覆盖系统环境变量。如果你想在多个测试套件中使用不同配置,这可能很有用。重要提示:必须在调用任何isRunning()创建规则实例的静态方法。变量值被应用到本次调用后创建的所有实例。 调用clearEnvironmentVariableOverrides()重置规则以使用默认值(包括任何实际的环境变量)。
在你的测试用例中,你可以使用经纪人运行在创建连接工厂时;getConnectionFactory()返回规则的RabbitMQ连接工厂. 以下示例展示了如何实现:
@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
JUnit5 条件
2.0.2 版本引入了对 JUnit5 的支持。
使用@RabbitAvailable注解
该类级注释类似于经纪运行 @Rule讨论内容见JUnit4@Rules. 它由兔子可用状态.
该注释具有三个性质:
-
队列:一组队列,在每次测试前声明(并清除),所有测试完成后删除。 -
管理:将此设为true如果你的测试也需要在代理上安装管理插件, -
清洗之后:(自2.2版本起)当true(默认),该队列检测间隙会被清除。
它用于检查代理是否可用,若不可用则跳过测试。如规则配置中所述,环境变量调用RABBITMQ_SERVER_REQUIRED如果true如果没有中介,测试会很快失败。你可以按照规则配置中讨论的环境变量来配置条件。
此外,兔子可用状态支持参数化测试构造器和方法的参数解析。支持两种参数类型:
-
BrokerRunningSupport: 实例(2.2之前,这是JUnit 4经纪运行实例) -
连接工厂:这BrokerRunningSupport实例的RabbitMQ连接工厂
以下示例展示了两者:
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {
private final ConnectionFactory connectionFactory;
public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
this.connectionFactory = brokerRunning.getConnectionFactory();
}
@Test
public void test(ConnectionFactory cf) throws Exception {
assertSame(cf, this.connectionFactory);
Connection conn = this.connectionFactory.newConnection();
Channel channel = conn.createChannel();
DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
assertEquals(0, declareOk.getConsumerCount());
channel.close();
conn.close();
}
}
前述测试在框架本身,验证参数注入以及条件是否正确创建了队列。
一个实用用户测试可能如下:
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {
private final CachingConnectionFactory connectionFactory;
public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
this.connectionFactory =
new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
@Test
public void test() throws Exception {
RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
...
}
}
当你在测试类中使用 Spring 注释应用上下文时,可以通过一个名为 Static 的方法获得条件连接工厂的引用RabbitAvailableCondition.getBrokerRunning().
从2.2版本开始,getBrokerRunning()返回 aBrokerRunningSupport对象; 之前,JUnit 4经纪人运行实例被返回。新类拥有相同的 API经纪运行. |
以下测试来自该框架,展示了其用法:
@RabbitAvailable(queues = {
RabbitTemplateMPPIntegrationTests.QUEUE,
RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {
public static final String QUEUE = "mpp.tests";
public static final String REPLIES = "mpp.tests.replies";
@Autowired
private RabbitTemplate template;
@Autowired
private Config config;
@Test
public void test() {
...
}
@Configuration
@EnableRabbit
public static class Config {
@Bean
public CachingConnectionFactory cf() {
return new CachingConnectionFactory(RabbitAvailableCondition
.getBrokerRunning()
.getConnectionFactory());
}
@Bean
public RabbitTemplate template() {
...
}
@Bean
public SimpleRabbitListenerContainerFactory
rabbitListenerContainerFactory() {
...
}
@RabbitListener(queues = QUEUE)
public byte[] foo(byte[] in) {
return in;
}
}
}
使用@LongRunning注解
类似于LongRunningIntegrationTestJUnit4@Rule,除非环境变量(或系统属性)设置为 ,否则此注释会跳过测试。true. 以下示例展示了如何使用它:
@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {
public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";
...
}
默认情况下,变量为RUN_LONG_INTEGRATION_TESTS但你可以在注释中指定变量名称值属性。