Improve support for disabling TestSupportBinderAutoConfiguration

Fixes #573

- Split TestSupportBinderAutoConfiguration into separate configs
  for the binder, binderfactory and message collector.
  This enables to address the testBinder as a regular binder when
  autoconfiguration is disabled, and to ensure that the message
  collector is available when the autoconfiguration is disabled.
- reorganize tests to use the default autoconfiguration options
- add documentation for disabling test binder autoconfiguration
This commit is contained in:
Marius Bogoevici
2017-07-13 14:38:27 -04:00
committed by Soby Chacko
parent bc7f794112
commit 2910d27e09
9 changed files with 203 additions and 20 deletions

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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<MessageChannel, ?, ?> messageChannelBinder = new TestSupportBinder();
@Bean
public BinderFactory binderFactory() {
public BinderFactory binderFactory(final Binder<MessageChannel, ?, ?> binder) {
return new BinderFactory() {
@Override
public <T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(
String configurationName, Class<? extends T> bindableType) {
return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) messageChannelBinder;
return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) binder;
}
};
}
@Bean
public MessageCollector messageCollector(BinderFactory binderFactory) {
return ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector();
}
}

View File

@@ -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<MessageChannel, ?, ?> messageChannelBinder = new TestSupportBinder();
@Bean
public Binder<MessageChannel, ?, ?> binder() {
return messageChannelBinder;
}
}

View File

@@ -1,2 +1,2 @@
test:\
org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration
org.springframework.cloud.stream.test.binder.TestSupportBinderConfiguration

View File

@@ -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

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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";
}
}
}