diff --git a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc index f1ddf44ee..48c296a3c 100644 --- a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc +++ b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc @@ -1997,6 +1997,31 @@ The bound interface is injected into the test so we can have access to both chan We are sending a message on the input channel and we are using the `MessageCollector` provided by Spring Cloud Stream's test support to capture the message has been sent to the output channel as a result. Once we have received the message, we can validate that the component functions correctly. +=== Disabling the test binder autoconfiguration + +The intent behind the test binder superseding all the other binders on the classpath is to make it easy to test your applications without making changes to your production dependencies. +In some cases (e.g. integration tests) it is useful to use the actual production binders instead, and that requires disabling the test binder autoconfiguration. +In order to do so, you can exclude the `org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration` class using one of the Spring Boot autoconfiguration exclusion mechanisms, as in the following example. + +[source,java] +---- + @SpringBootApplication(exclude = TestSupportBinderAutoConfiguration.class) + @EnableBinding(Processor.class) + public static class MyProcessor { + + @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) + public String transform(String in) { + return in + " world"; + } + } +---- + +When autoconfiguration is disabled, the test binder is available on the classpath, and its `defaultCandidate` property is set to `false`, so that it does not interfere with the regular user configuration. It can be referenced under the name `test` e.g.: + +---- +spring.cloud.stream.defaultBinder=test +---- + == Health Indicator Spring Cloud Stream provides a health indicator for binders. diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/MessageCollectorAutoConfiguration.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/MessageCollectorAutoConfiguration.java new file mode 100644 index 000000000..11031764b --- /dev/null +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/MessageCollectorAutoConfiguration.java @@ -0,0 +1,38 @@ +/* + * 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.cloud.stream.test.binder; + +import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.MessageChannel; + +/** + * Automatically registers the {@link MessageCollector} associated with the test binder as + * a bean. + * + * @author Marius Bogoevici + */ +@Configuration +public class MessageCollectorAutoConfiguration { + + @Bean + public MessageCollector messageCollector(BinderFactory binderFactory) { + return ((TestSupportBinder) binderFactory.getBinder("test", MessageChannel.class)).messageCollector(); + } + +} diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java index 1588c7c14..78d241ad3 100644 --- a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -23,6 +23,7 @@ import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.messaging.MessageChannel; @@ -34,29 +35,24 @@ import org.springframework.messaging.MessageChannel; * configuration, so adding this on the classpath in test scope is sufficient to have * support kick in and replace all binders with the test binder. * + * The test binder instance is supplied by the {@link TestSupportBinderConfiguration}. + * * @author Eric Bottard * @author Marius Bogoevici */ @Configuration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@Import(TestSupportBinderConfiguration.class) public class TestSupportBinderAutoConfiguration { - private Binder messageChannelBinder = new TestSupportBinder(); - @Bean - public BinderFactory binderFactory() { + public BinderFactory binderFactory(final Binder binder) { return new BinderFactory() { @Override public Binder getBinder( String configurationName, Class bindableType) { - return (Binder) messageChannelBinder; + return (Binder) binder; } }; } - - @Bean - public MessageCollector messageCollector(BinderFactory binderFactory) { - return ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector(); - } - } diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderConfiguration.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderConfiguration.java new file mode 100644 index 000000000..d5f8bf836 --- /dev/null +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderConfiguration.java @@ -0,0 +1,44 @@ +/* + * 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.cloud.stream.test.binder; + +import org.springframework.cloud.stream.binder.Binder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.MessageChannel; + +/** + * Binder {@link org.springframework.context.annotation.Configuration} for the + * {@link @TestBinder}. + * + * Either imported by the {@link TestSupportBinderAutoConfiguration} for the test binder + * default usage scenario (superseding all binders on the classpath), or used as a binder + * configuration on the classpath when test binder autoconfiguration is disabled. + * + * @author Marius Bogoevici + */ +@Configuration +public class TestSupportBinderConfiguration { + + private Binder messageChannelBinder = new TestSupportBinder(); + + @Bean + public Binder binder() { + return messageChannelBinder; + } + +} diff --git a/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.binders b/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.binders index 65f9ad3c4..f12cc2353 100644 --- a/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.binders +++ b/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.binders @@ -1,2 +1,2 @@ test:\ -org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration +org.springframework.cloud.stream.test.binder.TestSupportBinderConfiguration diff --git a/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.factories b/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.factories index 94bec2048..1bf7aa99f 100644 --- a/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-stream-test-support/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration:\ -org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration,\ +org.springframework.cloud.stream.test.binder.MessageCollectorAutoConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.stream.test.binder.TestBinderEnvironmentPostProcessor diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithBeanTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java similarity index 98% rename from spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithBeanTest.java rename to spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java index df1add7e0..d26d38402 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithBeanTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.stream.test.aggregate; +package org.springframework.cloud.stream.test.aggregate.bean; import java.util.concurrent.TimeUnit; diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithMainTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java similarity index 89% rename from spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithMainTest.java rename to spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java index b00cc88cb..870ac7eef 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/AggregateWithMainTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java @@ -14,19 +14,19 @@ * limitations under the License. */ -package org.springframework.cloud.stream.test.aggregate; +package org.springframework.cloud.stream.test.aggregate.main; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.aggregate.AggregateApplication; import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.test.binder.MessageCollector; -import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.Transformer; @@ -43,9 +43,10 @@ public class AggregateWithMainTest { @Test public void testAggregateApplication() throws InterruptedException { // emulate a main method - ConfigurableApplicationContext context = new AggregateApplicationBuilder( - TestSupportBinderAutoConfiguration.class).from(UppercaseProcessor.class) - .namespace("upper").to(SuffixProcessor.class).namespace("suffix").run(); + ConfigurableApplicationContext context = new AggregateApplicationBuilder(MainConfiguration.class) + .from(UppercaseProcessor.class).namespace("upper") + .to(SuffixProcessor.class).namespace("suffix") + .run(); AggregateApplication aggregateAccessor = context.getBean(AggregateApplication.class); MessageCollector messageCollector = context.getBean(MessageCollector.class); @@ -58,6 +59,10 @@ public class AggregateWithMainTest { context.close(); } + @SpringBootApplication + public static class MainConfiguration { + } + @Configuration @EnableBinding(Processor.class) public static class UppercaseProcessor { diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java new file mode 100644 index 000000000..c1f9b768d --- /dev/null +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java @@ -0,0 +1,74 @@ +/* + * 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.cloud.stream.test.disable; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.messaging.Processor; +import org.springframework.cloud.stream.test.binder.MessageCollector; +import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration; +import org.springframework.integration.annotation.Transformer; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marius Bogoevici + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = AutoconfigurationDisabledTest.MyProcessor.class, properties = { + "server.port=-1", + "spring.cloud.stream.defaultBinder=test" +}) +@DirtiesContext +public class AutoconfigurationDisabledTest { + + @Autowired + public MessageCollector messageCollector; + + @Autowired + public Processor processor; + + @Test + public void testAutoconfigurationDisabled() throws Exception { + processor.input().send(MessageBuilder.withPayload("Hello").build()); + // Since the interaction is synchronous, the result should be immediate + Message response = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + assertThat(response).isNotNull(); + assertThat(response.getPayload()).isEqualTo("Hello world"); + } + + @SpringBootApplication(exclude = TestSupportBinderAutoConfiguration.class) + @EnableBinding(Processor.class) + public static class MyProcessor { + + @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) + public String transform(String in) { + return in + " world"; + } + } +}