diff --git a/pom.xml b/pom.xml
index d8032b3..98063d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
test-embedded-kafka
kinesis-produce-consume
kstream
+ testing
diff --git a/testing/README.adoc b/testing/README.adoc
new file mode 100644
index 0000000..f58e331
--- /dev/null
+++ b/testing/README.adoc
@@ -0,0 +1,3 @@
+== How to test Spring Cloud Stream applications?
+
+This project contains a set of tests for simple Spring Cloud Stream applications to demonstrate what, how and when we can use to test this kind of microservices.
diff --git a/testing/pom.xml b/testing/pom.xml
new file mode 100644
index 0000000..c8e93dd
--- /dev/null
+++ b/testing/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ spring-cloud-stream-sample-testing
+ jar
+
+ testing
+ Demonstrating how to test spring cloud stream apps
+
+
+ org.springframework.cloud
+ spring-cloud-stream-samples
+ 1.2.0.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-kafka
+
+
+
+ org.springframework.integration
+ spring-integration-jdbc
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream-test-support
+ test
+
+
+
+ org.springframework.integration
+ spring-integration-test
+ test
+
+
+
+ org.hsqldb
+ hsqldb
+ test
+
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
diff --git a/testing/src/main/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessor.java b/testing/src/main/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessor.java
new file mode 100644
index 0000000..41b6f81
--- /dev/null
+++ b/testing/src/main/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessor.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.testing.processor;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.annotation.StreamListener;
+import org.springframework.cloud.stream.messaging.Processor;
+import org.springframework.messaging.handler.annotation.SendTo;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@SpringBootApplication
+@EnableBinding(Processor.class)
+public class ToUpperCaseProcessor {
+
+ @StreamListener(Processor.INPUT)
+ @SendTo(Processor.OUTPUT)
+ public String transform(String payload) {
+ return payload.toUpperCase();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(ToUpperCaseProcessor.class, args);
+ }
+
+}
diff --git a/testing/src/main/java/org/springframework/cloud/stream/testing/sink/JdbcSink.java b/testing/src/main/java/org/springframework/cloud/stream/testing/sink/JdbcSink.java
new file mode 100644
index 0000000..121c4d3
--- /dev/null
+++ b/testing/src/main/java/org/springframework/cloud/stream/testing/sink/JdbcSink.java
@@ -0,0 +1,48 @@
+/*
+ * 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.testing.sink;
+
+import javax.sql.DataSource;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.messaging.Sink;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.jdbc.JdbcMessageHandler;
+import org.springframework.messaging.MessageHandler;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@SpringBootApplication
+@EnableBinding(Sink.class)
+public class JdbcSink {
+
+ @Bean
+ @ServiceActivator(inputChannel = Sink.INPUT)
+ public MessageHandler logHandler(DataSource dataSource) {
+ return new JdbcMessageHandler(dataSource, "INSERT INTO foobar (value) VALUES (:payload)");
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(JdbcSink.class, args);
+ }
+
+}
diff --git a/testing/src/main/java/org/springframework/cloud/stream/testing/source/FooBarSource.java b/testing/src/main/java/org/springframework/cloud/stream/testing/source/FooBarSource.java
new file mode 100644
index 0000000..a960f5e
--- /dev/null
+++ b/testing/src/main/java/org/springframework/cloud/stream/testing/source/FooBarSource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.testing.source;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.messaging.Source;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.InboundChannelAdapter;
+import org.springframework.integration.annotation.Poller;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.messaging.support.GenericMessage;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@SpringBootApplication
+@EnableBinding(Source.class)
+public class FooBarSource {
+
+ private AtomicBoolean semaphore = new AtomicBoolean(true);
+
+ @Bean
+ @InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedDelay = "100"))
+ public MessageSource fooBarStrings() {
+ return () ->
+ new GenericMessage<>(this.semaphore.getAndSet(!this.semaphore.get()) ? "foo" : "bar");
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(FooBarSource.class, args);
+ }
+
+}
diff --git a/testing/src/test/java/org/springframework/cloud/stream/testing/processor/NaiveToUpperCaseTests.java b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/NaiveToUpperCaseTests.java
new file mode 100644
index 0000000..2367ebf
--- /dev/null
+++ b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/NaiveToUpperCaseTests.java
@@ -0,0 +1,34 @@
+/*
+ * 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.testing.processor;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+public class NaiveToUpperCaseTests {
+
+ @Test
+ public void testUpperCase() {
+ assertEquals("FOO", new ToUpperCaseProcessor().transform("foo"));
+ }
+
+}
diff --git a/testing/src/test/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessorTests.java b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessorTests.java
new file mode 100644
index 0000000..96fe5ec
--- /dev/null
+++ b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/ToUpperCaseProcessorTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.testing.processor;
+
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.springframework.cloud.stream.test.matcher.MessageQueueMatcher.receivesPayloadThat;
+
+import java.util.concurrent.BlockingQueue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.cloud.stream.messaging.Processor;
+import org.springframework.cloud.stream.test.binder.MessageCollector;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@DirtiesContext
+public class ToUpperCaseProcessorTests {
+
+ @Autowired
+ private Processor channels;
+
+ @Autowired
+ private MessageCollector collector;
+
+ @SpyBean
+ private ToUpperCaseProcessor toUpperCaseProcessor;
+
+ @Test
+ public void testMessages() {
+ this.channels.input().send(new GenericMessage<>("foo"));
+ this.channels.input().send(new GenericMessage<>("bar"));
+ this.channels.input().send(new GenericMessage<>("foo meets bar"));
+ this.channels.input().send(new GenericMessage<>("nothing but the best test"));
+
+ BlockingQueue> messages = this.collector.forChannel(channels.output());
+
+ assertThat(messages, receivesPayloadThat(is("FOO")));
+ assertThat(messages, receivesPayloadThat(is("BAR")));
+ assertThat(messages, receivesPayloadThat(is("FOO MEETS BAR")));
+ assertThat(messages, receivesPayloadThat(not("nothing but the best test")));
+
+ verify(this.toUpperCaseProcessor, times(4)).transform(anyString());
+ }
+
+}
diff --git a/testing/src/test/java/org/springframework/cloud/stream/testing/processor/integration/ToUpperCaseProcessorIntTests.java b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/integration/ToUpperCaseProcessorIntTests.java
new file mode 100644
index 0000000..6235f1f
--- /dev/null
+++ b/testing/src/test/java/org/springframework/cloud/stream/testing/processor/integration/ToUpperCaseProcessorIntTests.java
@@ -0,0 +1,91 @@
+/*
+ * 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.testing.processor.integration;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Iterator;
+
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.stream.testing.processor.ToUpperCaseProcessor;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.test.rule.KafkaEmbedded;
+import org.springframework.kafka.test.utils.KafkaTestUtils;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+ properties = {
+ "spring.autoconfigure.exclude=org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration",
+ "spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer",
+ "spring.cloud.stream.bindings.output.producer.headerMode=raw",
+ "spring.cloud.stream.bindings.input.consumer.headerMode=raw",
+ "spring.cloud.stream.bindings.input.group=embeddedKafkaApplication",
+ "spring.kafka.consumer.group-id=EmbeddedKafkaIntTest"
+ },
+ classes = ToUpperCaseProcessor.class,
+ webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@DirtiesContext
+public class ToUpperCaseProcessorIntTests {
+
+ @ClassRule
+ public static KafkaEmbedded kafkaEmbedded = new KafkaEmbedded(1, true, "output");
+
+ @Autowired
+ private KafkaTemplate template;
+
+ @Autowired
+ private DefaultKafkaConsumerFactory consumerFactory;
+
+ @BeforeClass
+ public static void setup() {
+ System.setProperty("spring.kafka.bootstrap-servers", kafkaEmbedded.getBrokersAsString());
+ System.setProperty("spring.cloud.stream.kafka.binder.zkNodes", kafkaEmbedded.getZookeeperConnectionString());
+ }
+
+ @Test
+ public void testMessagesOverKafka() throws Exception {
+ this.template.send("input", "bar".getBytes());
+
+ Consumer consumer = this.consumerFactory.createConsumer();
+
+ kafkaEmbedded.consumeFromAnEmbeddedTopic(consumer, "output");
+
+ ConsumerRecords replies = KafkaTestUtils.getRecords(consumer);
+ assertThat(replies.count()).isEqualTo(1);
+
+ Iterator> iterator = replies.iterator();
+ assertThat(iterator.next().value()).isEqualTo("BAR");
+ }
+
+}
diff --git a/testing/src/test/java/org/springframework/cloud/stream/testing/sink/JdbcSinkTests.java b/testing/src/test/java/org/springframework/cloud/stream/testing/sink/JdbcSinkTests.java
new file mode 100644
index 0000000..02e27f5
--- /dev/null
+++ b/testing/src/test/java/org/springframework/cloud/stream/testing/sink/JdbcSinkTests.java
@@ -0,0 +1,62 @@
+/*
+ * 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.testing.sink;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.stream.messaging.Sink;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author Artem Bilan
+ *
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@DirtiesContext
+public class JdbcSinkTests {
+
+ @Autowired
+ private Sink channels;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Test
+ public void testMessages() {
+ this.channels.input().send(new GenericMessage<>("foo"));
+ this.channels.input().send(new GenericMessage<>("bar"));
+
+ List