AMQP-791: Support JUnit5

JIRA: https://jira.spring.io/browse/AMQP-791

- refactor `BrokerRunning` JUnit4 `@Rule` so it can be invoked from an `ExecutionCondition`.
- Add `@RabbitAvailable` annotation with queue list and auto-delete queues at the end of the class; purge them between tests.

* Implement `ParameterResolver` to access the rule's connection factory.

* Support CTOR Injection and `BrokerRunning` Injection

- user might want to invoke methods such as `deleteQueues()`.

* Patches omitted from previous commit

* WIP - Spring

* Polishing - PR Comments

* Convert `RabbitTemplateMPPIntegrationTests` - JUnit5

* Remove bogus test

* Docs + `@LongRunning`

* Polishing - PR Comments
This commit is contained in:
Gary Russell
2017-12-20 14:43:35 -05:00
committed by Artem Bilan
parent 48412b93f6
commit d73f4ffc0a
15 changed files with 823 additions and 167 deletions

View File

@@ -8,6 +8,7 @@ buildscript {
classpath 'me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1'
classpath 'io.spring.gradle:dependency-management-plugin:1.0.2.RELEASE'
classpath 'io.spring.gradle:spring-io-plugin:0.0.8.RELEASE'
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.2'
}
}
@@ -87,7 +88,10 @@ subprojects { subproject ->
ext {
hamcrestVersion = '1.3'
jackson2Version = '2.9.1'
junitVersion = '4.12'
junit4Version = '4.12'
junitJupiterVersion = '5.0.2'
junitPlatformVersion = '1.0.2'
junitVintageVersion = '4.12.2'
log4jVersion = '2.8.2'
logbackVersion = '1.2.3'
mockitoVersion = '2.11.0'
@@ -112,7 +116,7 @@ subprojects { subproject ->
// dependencies that are common across all java projects
dependencies {
testCompile ("junit:junit:$junitVersion") {
testCompile ("junit:junit:$junit4Version") {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
testCompile "org.apache.logging.log4j:log4j-core:$log4jVersion"
@@ -123,6 +127,18 @@ subprojects { subproject ->
testCompile "org.springframework:spring-test:$springVersion"
testCompile "org.slf4j:slf4j-log4j12:$slf4jVersion" // amqp-client now uses SLF4J
testRuntime "org.apache.logging.log4j:log4j-jcl:$log4jVersion"
testCompile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testRuntime "org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}"
testRuntime "org.junit.platform:junit-platform-commons:${junitPlatformVersion}"
testRuntime "org.junit.platform:junit-platform-launcher:${junitPlatformVersion}"
// To support JUnit 4 tests
testRuntime "org.junit.vintage:junit-vintage-engine:${junitVintageVersion}"
// To avoid compiler warnings about @API annotations in JUnit code
testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0'
}
// enable all compiler warnings; individual projects may customize further
@@ -265,12 +281,15 @@ project('spring-rabbit-junit') {
dependencies { // no spring-amqp dependencies allowed
compile "org.springframework:spring-core:$springVersion"
compile "junit:junit:$junitVersion"
compile "junit:junit:$junit4Version"
compile "com.rabbitmq:amqp-client:$rabbitmqVersion"
compile ("com.rabbitmq:http-client:$rabbitmqHttpClientVersion") {
exclude group: 'org.springframework', module: 'spring-web'
}
compile "org.springframework:spring-web:$springVersion"
compile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
compile "org.junit.platform:junit-platform-commons:${junitPlatformVersion}"
compileOnly 'org.apiguardian:apiguardian-api:1.0.0'
}
@@ -282,7 +301,7 @@ project('spring-rabbit-test') {
dependencies {
compile project(":spring-rabbit")
compile ("junit:junit:$junitVersion") {
compile ("junit:junit:$junit4Version") {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
compile "org.hamcrest:hamcrest-all:$hamcrestVersion"

View File

@@ -19,6 +19,8 @@ package org.springframework.amqp.rabbit.junit;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -367,38 +369,8 @@ public final class BrokerRunning extends TestWatcher {
Channel channel = null;
try {
connection = connectionFactory.newConnection();
connection.setId(generateId());
channel = connection.createChannel();
for (String queueName : this.queues) {
if (this.purge) {
logger.debug("Deleting queue: " + queueName);
// Delete completely - gets rid of consumers and bindings as well
channel.queueDelete(queueName);
}
if (isDefaultQueue(queueName)) {
// Just for test probe.
channel.queueDelete(queueName);
}
else {
channel.queueDeclare(queueName, true, false, false, null);
}
}
brokerOffline.put(this.port, false);
if (!this.assumeOnline) {
Assume.assumeTrue(brokerOffline.get(this.port));
}
if (this.management) {
Client client = new Client(getAdminUri(), this.adminUser, this.adminPassword);
if (!client.alivenessTest("/")) {
throw new RuntimeException("Aliveness test failed for localhost:15672 guest/quest; "
+ "management not available");
}
}
connection = getConnection(connectionFactory);
channel = createQueues(connection);
}
catch (Exception e) {
logger.warn("Not executing tests because basic connectivity test failed: " + e.getMessage());
@@ -419,7 +391,59 @@ public final class BrokerRunning extends TestWatcher {
return super.apply(base, description);
}
private boolean fatal() {
public void isUp() throws Exception {
Connection connection = getConnectionFactory().newConnection();
Channel channel = null;
try {
channel = createQueues(connection);
}
finally {
closeResources(connection, channel);
}
}
private Connection getConnection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
Connection connection = connectionFactory.newConnection();
connection.setId(generateId());
return connection;
}
private Channel createQueues(Connection connection) throws IOException, MalformedURLException, URISyntaxException {
Channel channel;
channel = connection.createChannel();
for (String queueName : this.queues) {
if (this.purge) {
logger.debug("Deleting queue: " + queueName);
// Delete completely - gets rid of consumers and bindings as well
channel.queueDelete(queueName);
}
if (isDefaultQueue(queueName)) {
// Just for test probe.
channel.queueDelete(queueName);
}
else {
channel.queueDeclare(queueName, true, false, false, null);
}
}
brokerOffline.put(this.port, false);
if (!this.assumeOnline) {
Assume.assumeTrue(brokerOffline.get(this.port));
}
if (this.management) {
Client client = new Client(getAdminUri(), this.adminUser, this.adminPassword);
if (!client.alivenessTest("/")) {
throw new RuntimeException("Aliveness test failed for localhost:15672 guest/quest; "
+ "management not available");
}
}
return channel;
}
public static boolean fatal() {
String serversRequired = System.getenv(BROKER_REQUIRED);
if (Boolean.parseBoolean(serversRequired)) {
logger.error("RABBITMQ IS REQUIRED BUT NOT AVAILABLE");
@@ -465,7 +489,7 @@ public final class BrokerRunning extends TestWatcher {
Channel channel = null;
try {
connection = connectionFactory.newConnection();
connection = getConnection(connectionFactory);
connection.setId(generateId() + ".queueDelete");
channel = connection.createChannel();
@@ -491,7 +515,7 @@ public final class BrokerRunning extends TestWatcher {
Channel channel = null;
try {
connection = connectionFactory.newConnection();
connection = getConnection(connectionFactory);
connection.setId(generateId() + ".queueDelete");
channel = connection.createChannel();
@@ -517,7 +541,7 @@ public final class BrokerRunning extends TestWatcher {
Channel channel = null;
try {
connection = connectionFactory.newConnection();
connection = getConnection(connectionFactory);
connection.setId(generateId() + ".exchangeDelete");
channel = connection.createChannel();

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Test classes annotated with this will not run if an environment variable or system
* property (default {@code RUN_LONG_INTEGRATION_TESTS}) is not present or does not have
* the value that {@link Boolean#parseBoolean(String)} evaluates to {@code true}.
*
* @author Gary Russell
* @since 2.0.2
*
*/
@ExtendWith(LongRunningIntegrationTestCondition.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LongRunning {
/**
* The name of the variable/property used to determine whether long runnning tests
* should run.
* @return the name of the variable/property.
*/
String value() default LongRunningIntegrationTest.RUN_LONG_INTEGRATION_TESTS;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,12 +36,21 @@ public class LongRunningIntegrationTest extends TestWatcher {
private final static Log logger = LogFactory.getLog(LongRunningIntegrationTest.class);
private static final String RUN_LONG_PROP = "RUN_LONG_INTEGRATION_TESTS";
public static final String RUN_LONG_INTEGRATION_TESTS = "RUN_LONG_INTEGRATION_TESTS";
private boolean shouldRun = false;
public LongRunningIntegrationTest() {
for (String value: new String[] { System.getenv(RUN_LONG_PROP), System.getProperty(RUN_LONG_PROP) }) {
this(RUN_LONG_INTEGRATION_TESTS);
}
/**
* Check using a custom variable/property name.
* @param property the variable/property name.
* @since 2.0.2
*/
public LongRunningIntegrationTest(String property) {
for (String value: new String[] { System.getenv(property), System.getProperty(property) }) {
if (Boolean.parseBoolean(value)) {
this.shouldRun = true;
break;
@@ -58,4 +67,12 @@ public class LongRunningIntegrationTest extends TestWatcher {
return super.apply(base, description);
}
/**
* Return true if the test should run.
* @return true to run.
*/
public boolean isShouldRun() {
return this.shouldRun;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import java.lang.reflect.AnnotatedElement;
import java.util.Optional;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.util.StringUtils;
/**
* {@link ExecutionCondition} to skip long running tests unless an environment
* variable or property is set.
*
* @author Gary Russell
* @since 2.0.2
* @see LongRunning
*/
public class LongRunningIntegrationTestCondition implements ExecutionCondition {
private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled(
"@LongRunning is not present");
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<AnnotatedElement> element = context.getElement();
Optional<LongRunning> longRunning = AnnotationUtils.findAnnotation(element, LongRunning.class);
if (longRunning.isPresent()) {
String property = longRunning.get().value();
if (!StringUtils.hasText(property)) {
property = LongRunningIntegrationTest.RUN_LONG_INTEGRATION_TESTS;
}
LongRunningIntegrationTest lrit = new LongRunningIntegrationTest(property);
return lrit.isShouldRun() ? ConditionEvaluationResult.enabled("Long running tests must run")
: ConditionEvaluationResult.disabled("Long running tests are skipped");
}
return ENABLED;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Test classes annotated with this will not run if there is no broker on localhost.
*
* @author Gary Russell
* @since 2.0.2
*
*/
@ExtendWith(RabbitAvailableCondition.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RabbitAvailable {
/**
* The queues to create and ensure empty; they will be deleted after the test class
* completes.
* @return the queues.
*/
String[] queues() default {};
/**
* Requires the management plugin to be available.
* @return true to require a management plugin.
*/
boolean management() default false;
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import java.lang.reflect.AnnotatedElement;
import java.util.Optional;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.util.Assert;
import com.rabbitmq.client.ConnectionFactory;
/**
* JUnit5 {@link ExecutionCondition}.
* Looks for {@code @RabbitAvailable} annotated classes and disables
* if found the broker is not available.
*
* @author Gary Russell
* @since 2.0.2
*
*/
public class RabbitAvailableCondition implements ExecutionCondition, AfterAllCallback, ParameterResolver {
private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled(
"@RabbitAvailable is not present");
private static final ThreadLocal<BrokerRunning> brokerRunningHolder = new ThreadLocal<>();
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<AnnotatedElement> element = context.getElement();
Optional<RabbitAvailable> rabbit = AnnotationUtils.findAnnotation(element, RabbitAvailable.class);
if (rabbit.isPresent()) {
try {
String[] queues = rabbit.get().queues();
BrokerRunning brokerRunning = getStore(context).get("brokerRunning", BrokerRunning.class);
if (brokerRunning == null) {
if (rabbit.get().management()) {
brokerRunning = BrokerRunning.isBrokerAndManagementRunningWithEmptyQueues(queues);
}
else {
brokerRunning = BrokerRunning.isRunningWithEmptyQueues(queues);
}
}
brokerRunning.isUp();
brokerRunningHolder.set(brokerRunning);
Store store = getStore(context);
store.put("brokerRunning", brokerRunning);
store.put("queuesToDelete", queues);
return ConditionEvaluationResult.enabled("RabbitMQ is available");
}
catch (Exception e) {
if (BrokerRunning.fatal()) {
throw new IllegalStateException("Required RabbitMQ is not available");
}
return ConditionEvaluationResult.disabled("RabbitMQ is not available");
}
}
return ENABLED;
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
brokerRunningHolder.remove();
Store store = getStore(context);
BrokerRunning brokerRunning = store.remove("brokerRunning", BrokerRunning.class);
if (brokerRunning != null) {
brokerRunning.removeTestQueues();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Class<?> type = parameterContext.getParameter().getType();
return type.equals(ConnectionFactory.class) || type.equals(BrokerRunning.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context)
throws ParameterResolutionException {
// in parent for method injection, Composite key causes a store miss
BrokerRunning brokerRunning =
getParentStore(context).get("brokerRunning", BrokerRunning.class) == null
? getStore(context).get("brokerRunning", BrokerRunning.class)
: getParentStore(context).get("brokerRunning", BrokerRunning.class);
Assert.state(brokerRunning != null, "Could not find brokerRunning instance");
Class<?> type = parameterContext.getParameter().getType();
return type.equals(ConnectionFactory.class) ? brokerRunning.getConnectionFactory()
: brokerRunning;
}
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context));
}
private Store getParentStore(ExtensionContext context) {
ExtensionContext parent = context.getParent().get();
return parent.getStore(Namespace.create(getClass(), parent));
}
public static BrokerRunning getBrokerRunning() {
return brokerRunningHolder.get();
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %5p %c [%t] : %m%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.amqp.rabbit" level="info"/>
<Logger name="org.springframework.beans.factory" level="info"/>
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import org.junit.jupiter.api.Test;
/**
* @author Gary Russell
* @since 2.0.2
*
*/
@LongRunning("LongRunningIntegrationTestsConditionTests")
public class LongRunningIntegrationTestsConditionTests {
@Test
public void test() {
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import org.junit.jupiter.api.Test;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author Gary Russell
* @since 2.0.2
*
*/
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {
private final ConnectionFactory connectionFactory;
public RabbitAvailableCTORInjectionTests(BrokerRunning 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();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.junit;
import static org.junit.Assert.assertEquals;
import org.junit.jupiter.api.Test;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author Gary Russell
* @since 2.0.2
*
*/
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableTests {
@Test
public void test(ConnectionFactory connectionFactory) throws Exception {
Connection conn = connectionFactory.newConnection();
Channel channel = conn.createChannel();
DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
assertEquals(0, declareOk.getConsumerCount());
channel.close();
conn.close();
}
}

View File

@@ -18,10 +18,7 @@ package org.springframework.amqp.rabbit.core;
import static org.junit.Assert.assertTrue;
import org.junit.AfterClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
@@ -30,30 +27,31 @@ import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.amqp.rabbit.junit.RabbitAvailableCondition;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
* @since 1.7.6
*
*/
@RunWith(SpringRunner.class)
@RabbitAvailable(queues = {
RabbitTemplateMPPIntegrationTests.QUEUE,
RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {
private static final String QUEUE = "mpp.tests";
public static final String QUEUE = "mpp.tests";
private static final String REPLIES = "mpp.tests.replies";
@ClassRule
public static BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(QUEUE, REPLIES);
public static final String REPLIES = "mpp.tests.replies";
@Autowired
private RabbitTemplate template;
@@ -61,11 +59,6 @@ public class RabbitTemplateMPPIntegrationTests {
@Autowired
private Config config;
@AfterClass
public static void tearDown() {
brokerIsRunning.removeTestQueues();
}
@Test // 2.0.x only
public void testMPPsAppliedDirectReplyToContainerTests() {
this.template.sendAndReceive(new Message("foo".getBytes(), new MessageProperties()));
@@ -119,7 +112,8 @@ public class RabbitTemplateMPPIntegrationTests {
@Bean
public CachingConnectionFactory cf() {
return new CachingConnectionFactory(brokerIsRunning.getConnectionFactory());
return new CachingConnectionFactory(RabbitAvailableCondition.getBrokerRunning()
.getConnectionFactory());
}
@Bean

View File

@@ -23,40 +23,40 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.SingleConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.LongRunningIntegrationTest;
import org.springframework.amqp.rabbit.junit.LongRunning;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.utils.test.TestUtils;
import org.springframework.test.util.ReflectionTestUtils;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author Gary Russell
*
* @since 1.2.1
*
*/
@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {
public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";
private final Log logger = LogFactory.getLog(SimpleMessageListenerContainerLongTests.class);
@Rule
public LongRunningIntegrationTest longTest = new LongRunningIntegrationTest();
private final SingleConnectionFactory connectionFactory;
@Rule
public BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo");
@After
public void tearDown() {
this.brokerRunning.removeTestQueues();
public SimpleMessageListenerContainerLongTests(ConnectionFactory connectionFactory) {
this.connectionFactory = new SingleConnectionFactory(connectionFactory);
}
@Test
@@ -70,11 +70,11 @@ public class SimpleMessageListenerContainerLongTests {
}
private void testChangeConsumerCountGuts(boolean transacted) throws Exception {
final SingleConnectionFactory singleConnectionFactory = new SingleConnectionFactory("localhost");
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(singleConnectionFactory);
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer(this.connectionFactory);
try {
container.setMessageListener(new MessageListenerAdapter(this));
container.setQueueNames("foo");
container.setQueueNames(QUEUE);
container.setAutoStartup(false);
container.setConcurrentConsumers(2);
container.setChannelTransacted(transacted);
@@ -85,9 +85,9 @@ public class SimpleMessageListenerContainerLongTests {
container.setConcurrentConsumers(1);
waitForNConsumers(container, 1);
container.setMaxConcurrentConsumers(3);
RabbitTemplate template = new RabbitTemplate(singleConnectionFactory);
RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
for (int i = 0; i < 20; i++) {
template.convertAndSend("foo", "foo");
template.convertAndSend(QUEUE, "foo");
}
waitForNConsumers(container, 2); // increased consumers due to work
waitForNConsumers(container, 1, 20000); // should stop the extra consumer after 10 seconds idle
@@ -95,7 +95,7 @@ public class SimpleMessageListenerContainerLongTests {
waitForNConsumers(container, 3);
container.stop();
waitForNConsumers(container, 0);
singleConnectionFactory.destroy();
this.connectionFactory.destroy();
}
finally {
container.stop();
@@ -104,13 +104,13 @@ public class SimpleMessageListenerContainerLongTests {
@Test
public void testAddQueuesAndStartInCycle() throws Exception {
final SingleConnectionFactory connectionFactory = new SingleConnectionFactory("localhost");
final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(
this.connectionFactory);
container.setMessageListener((MessageListener) message -> { });
container.setConcurrentConsumers(2);
container.afterPropertiesSet();
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
RabbitAdmin admin = new RabbitAdmin(this.connectionFactory);
for (int i = 0; i < 20; i++) {
Queue queue = new Queue("testAddQueuesAndStartInCycle" + i);
admin.declareQueue(queue);

View File

@@ -4569,7 +4569,8 @@ The default retry properties (3 retries at 5 second intervals) can be overridden
a| image::images/tickmark.png[]
a| image::images/tickmark.png[]
| possibleAuthenticationFailureFatal
| possibleAuthentication
FailureFatal
(possible-authentication-failure-fatal)
a| When set to `true` (default), if a `PossibleAuthenticationFailureException` is thrown during connection, it is considered fatal.
@@ -4587,7 +4588,10 @@ You can also use a properties bean to set the property globally for all containe
[source,xml]
----
<util:properties id="spring.amqp.global.properties">
<prop key="mlc.possible.authentication.failure.fatal">false</prop>
<prop
key="mlc.possible.authentication.failure.fatal">
false
</prop>
</util:properties>
----
@@ -4750,7 +4754,8 @@ a| image::images/tickmark.png[]
a| image::images/tickmark.png[]
a| image::images/tickmark.png[]
| afterReceivePostProcessors
| afterReceive
PostProcessors
(N/A)
| An array of `MessagePostProcessor` s which are invoked, before invoking the listener.
@@ -4955,7 +4960,8 @@ The `deliveryMode` (or any other properties) can be changed in the `additionalHe
RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") {
protected Map<? extends String, ? extends Object> additionalHeaders(Message message, Throwable cause) {
message.getMessageProperties().setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
message.getMessageProperties()
.setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
return null;
}

View File

@@ -278,112 +278,113 @@ Here is a simple test case that uses the template:
@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {
@Autowired
private TestRabbitTemplate template;
@Autowired
private TestRabbitTemplate template;
@Autowired
private Config config;
@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"));
@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"));
}
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"));
}
@Test
public void testSendAndReceive() {
assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
}
----
[source, java]
----
@Configuration
@EnableRabbit
public static class Config {
@Configuration
@EnableRabbit
public static class Config {
public String fooIn = "";
public String fooIn = "";
public String barIn = "";
public String barIn = "";
public String smlc1In = "smlc1:";
public String smlc1In = "smlc1:";
@Bean
public TestRabbitTemplate template() throws IOException {
return new TestRabbitTemplate(connectionFactory());
}
@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 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;
}
@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 = "foo")
public void foo(String in) {
this.fooIn += "foo:" + in;
}
@RabbitListener(queues = "bar")
public void bar(String in) {
this.barIn += "bar:" + in;
}
@RabbitListener(queues = "bar")
public void bar(String in) {
this.barIn += "bar:" + in;
}
@RabbitListener(queues = "baz")
public String baz(String in) {
return "baz:" + 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() {
@Bean
public SimpleMessageListenerContainer smlc1() throws IOException {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
container.setQueueNames("foo", "bar");
container.setMessageListener(new MessageListenerAdapter(new Object() {
@SuppressWarnings("unused")
public void handleMessage(String in) {
smlc1In += in;
}
@SuppressWarnings("unused")
public void handleMessage(String in) {
smlc1In += in;
}
}));
return container;
}
}));
return container;
}
}
}
}
----
[[junit-rules]]
==== JUnit @Rules
[[junit4-rules]]
==== JUnit4 @Rules
Spring AMQP _version 1.7_ provides an additional jar `spring-rabbit-junit`; this jar contains a couple of utility `@Rule` s for use when running JUnit tests.
Spring AMQP _version 1.7_ and later provide an additional jar `spring-rabbit-junit`; this jar contains a couple of utility `@Rule` s for use when running JUnit4 tests.
See <<junit5-conditions>> for JUnit5 testing.
===== BrokerRunning
@@ -407,6 +408,7 @@ public static void tearDown() {
There are several `isRunning...` static methods such as `isBrokerAndManagementRunning()` which verifies the broker has the management plugin enabled.
[[brokerRunning-configure]]
====== Configuring the Rule
There are times when you want tests to fail if there is no broker, such as a nightly CI build.
@@ -485,3 +487,159 @@ public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();
----
To disable the rule at runtime, set an environment variable `RUN_LONG_INTEGRATION_TESTS` to `true`.
[[junit5-conditions]]
==== JUnit5 Conditions
Version _2.0.2_ introduced support for JUnit5.
===== @RabbitAvailable Annotation
This class-level annotation is similar to the `BrokerRunning` `@Rule` discussed in <<junit4-rules>>; it is processed by the `RabbitAvailableCondition`.
The annotation has two properties:
* `queues` - an array of queues that will be declared (and purged) before each test and deleted when all tests are complete.
* `management` - set to `true` if your tests also require the management plugin installed on the broker.
It is used to check if the broker is available and skip the tests if not.
As discussed in <<brokerRunning-configure>> the environment variable `RABBITMQ_SERVER_REQUIRED`, if `true` will cause the tests to fail fast if there is no broker.
The condition can be configured using environment variables as discussed in <<brokerRunning-configure>>.
In addition, the `RabbitAvailableCondition` supports argument resolution for parameterized test constructors and methods.
Two argument types are supported `BrokerRunning` (the instance) and `ConnectionFactory` - the `BrokerRunning` 's RabbitMQ connection factory.
Here is an example of each:
[source, java]
----
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {
private final ConnectionFactory connectionFactory;
public RabbitAvailableCTORInjectionTests(BrokerRunning 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();
}
}
----
This test is in the framework itself and verifies the argument injection and that the condition created the queue properly.
A practical user test might be:
[source, java]
----
@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {
private final CachingConnectionFactory connectionFactory;
public RabbitAvailableCTORInjectionTests(BrokerRunning brokerRunning) {
this.connectionFactory =
new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
@Test
public void test() throws Exception {
RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
...
}
}
----
When using a Spring annotation application context within a test class, it is also possible to get a reference to the condition's connection factory via a static method `RabbitAvailableCondition.getBrokerRunning()`.
Here is another test from the framework that demonstrates the usage:
[source, java]
----
@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 Annotation
Similar to the `LongRunningIntegrationTest` JUnit4 `@Rule`, this annotation causes tests to be skipped unless an environment variable (or system property) is set to `true`.
[source, java]
----
@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {
public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";
...
}
----
By default, the variable is `RUN_LONG_INTEGRATION_TESTS` but the variable name can be specified in the annotation's `value` attribute.