In this tutorial, we keep the contracts together with the producer code. On the consumer side, we be using Service Discovery.

Scenarios

beer 1
Figure 1. Positive beer selling via HTTP

   

beer 2
Figure 2. Negative beer selling via HTTP

   

Flow

flow
Figure 3. Consumer Driven Contract flow

Tutorial

Using Consumer Driven Contracts is like using TDD at the architecture level. We start by writing a test on the consumer side.

Consumer flow 1

consumer flow 1
Figure 4. Interact with cloned producer code

Let’s go back to our consumer code. We need to look at BeerControllerTest and BeerController. We know how we would like the API to look, so now we can write the missing implementation in the BeerController.

Tip
Remember that, in order to use the load balancing features, we need to add the org.springframework.cloud:spring-cloud-starter-netflix-eureka-client dependency.
Maven
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Gradle
implementation("org.springframework.cloud:spring-cloud-starter-netflix-eureka-client")

Also, you need to register the RestTemplate bean as @LoadBalanced. Go to the ClientApplication class and register the bean as follows:

@Bean
@LoadBalanced
RestTemplate restTemplate() {
    return new RestTemplate();
}

Now let’s assume that we call the producer application by using the http://somenameforproducer/check URL. You need to provide some properties to tell Stub Runner that the given service name (in our case, somenameforproducer) should be mapped to the running HTTP server stub of a given producer. Let’s set those properties. Stub Runner requires you to set the stubrunner.idsToServiceIds mapping in which the key is the artifact ID and the value is the service name in the code. You also need to define the idsToServiceIds property, as shown in the following code:

stubrunner:
  idsToServiceIds:
    beer-api-producer: somenameforproducer

When you call the URL http://somenameforproducer/check, it will get redirected to a fake HTTP instance that was started with the beer-api-producer stubs JAR. We know how the API needs to look, so we can now go ahead and try to write the missing implementation of the BeerController.

Writing the Missing Consumer HTTP Implementation

  • Let’s go back to our, consumer’s code - let’s go back to the BeerControllerTest and BeerController. We know how we would like the API to look like so now we can write the missing implementation in the BeerController. Let’s assume that the producer application will be running at http://localhost:8090/. Now go ahead and try to write the missing implementation of the BeerController

  • In case of any issues you can check out the solution

Turning on Stub Runner in HTTP Consumer Tests

  • Now it’s time to turn on the magic! Let’s add the Spring Cloud Starter Contract Stub Runner test dependency.

    Maven
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    	<scope>test</scope>
    </dependency>
    Gradle
    testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
  • We can annotate our test class with @AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.LOCAL, ids = "com.example:beer-api-producer:+:stubs:8090"). What that will do is:

    • it will download the stub JARs from Maven local (stubsMode = StubRunnerProperties.StubsMode.LOCAL)

    • it will search for a JAR with coordinates com.example:beer-api-producer with latest version (+) and stubs classifier. Once it’s found the fake HTTP server stub will be started at port 8090

  • Rerun the test - it should automagically pass!

    • In the logs you will see information about downloading, unpacking and starting stubs (see the logs)

    • What happened is that we could interact with real API without writing a single line of production code

Playing with the HTTP Contracts

  • TDD is about red, green and refactor. We went through the first two. Time to refactor the code. We come to the conclusion that the name field is unnecessary. In the BeerController.java file let’s create a new class called BeerRequest that will contain only age field and let’s try to send that to our stubbed producer. (Show solution)

  • Let’s run the tests again - what will happen is that the tests will fail. That’s because in the contract you have explicitly described that you want the name to be there. As you can see all the typos will be caught during the build time of your project.

    • The same will happen if you leave the name but change the age to some other value (e.g. 28). Our stubs at the moment are very strict. We’ll try to fix that in a second

  • To fix this you need to go back with your IDE to the producer and modify your HTTP contracts.

    • Just remove the name field from the request body.

    • Spring Cloud Contract allows you to provide dynamic values for parts of body, urls, headers etc. This is especially useful when working with dates, database ids, UUIDs etc.

    • Let’s open the shouldGrantABeerIfOldEnough.groovy and go to the request body age element

    • Instead of 22 write $(regex('[2-9][0-9]')). Now let’s analyze what this is.

      • In order to tell Spring Cloud Contract that there will be a dynamic value set you have to use either the $() or value() method. They are equivalent.

      • Next we use regex() method that converts your String into Pattern. In this case we assume a 2 digit number greater or equal to 20

    • Repeat the same process for the shouldRejectABeerIfTooYoung.groovy contract but change the regular expression to [0-1][0-9]

    • Run the building with test skipping and check the output of stubs. You’ll see that the generated mappings have changed from equality check in JSON Path to regular expression check

    • Go back to the consumer code and run the BeerControlerTest again. This time it should pass. You can also change the values of age to e.g. 45 for the positive case and 11 for the negative on.

Congratulations! As a consumer, you have successfully used the API of the producer for both HTTP and messaging. Now you can file a pull request (PR) to the producer code with the proposal of the contract,

Let’s switch to the producer side.

Producer Flow 1

producer flow 1
Figure 5. Producer takes over the PR, writes missing impl and merges the PR

IDE setup

  • Open in your IDE the producer project (either via Maven or Gradle)

  • We’re assuming that we’ve taken over the PR. Example of how to achieve that in "real life" for a PR that got submitted to via a branch called the_pr looks like this:

git fetch origin
git checkout -b the_pr origin/the_pr
git merge master
  • The idea of Spring Cloud Contract is about stub and contract validity. Right now we have a set of contracts defined but we haven’t tested it against the producer side. Time to change that!

Setting up the Spring Cloud Contract plugin

  • Spring Cloud Contract can generate tests from your contracts to ensure that your implementation’s API is compatible with the defined contract. Let’s set up the project to start generating tests.

    • Spring Cloud Contract needs a base class that all of the generated tests will extend. Currently we support 3 different ways of defining a base class (you can read more about this in the Spring Cloud Contract documentation for Gradle and Maven)

      • a single class for all tests

      • convention based naming (takes 2 last package names and appends Base. Having a contract src/test/resources/contracts/foo/bar/shouldDoSth.groovy would create a test class called BarTest that would extend FooBarBase class.

      • manual mapping (you can state that contracts matching certain regular expression will have to have a base class with fully qualified name equal to X)

    • In the following example we’ll play with convention based naming

      • For Maven under the plugin setup you have to set up the plugin configuration <configuration><packageWithBaseClasses>com.example</packageWithBaseClasses></configuration>

        Maven
        <plugin>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-maven-plugin</artifactId>
            <version>${spring-cloud-contract.version}</version>
            <extensions>true</extensions>
            <configuration>
                <packageWithBaseClasses>com.example</packageWithBaseClasses>
            </configuration>
        </plugin>
        Gradle
        contracts {
        	testFramework = "JUNIT5"
            packageWithBaseClasses = 'com.example'
        }
      • In both cases passing of that value tells the plugin that a given base class is available under the com.example package

Updating Contracts from the PR

Generating tests from contracts

  • Let’s generate the tests! Just call:

    Maven
    $ ./mvnw clean install
    Gradle
    $ ./gradlew clean build publishToMavenLocal
    • Suddenly some tests should start failing. Those tests are the autogenerated tests created by Spring Cloud Contract

    • The tests lay under /generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/beer in target for Maven or build for Gradle

    • There will be a test for each folder in which you store your contracts. The name of the test class will be the name of that folder

    • Each of the contracts will be a single test inside that test class

    • If you check out the generated tests you’ll notice that the dynamic parts of the request part of the contract got converted to a concrete value. Any dynamic bits on the response side would be converted into matchers.

  • Time to fix the broken tests. We need to do that by providing the missing implementation.

Fixing broken HTTP tests

  • Let’s start with HTTP

    • First let’s write the missing implementation in ProducerController. The logic to be written is extremely simple - if the personCheckingService.shouldGetBeer(…​) returns true then we should return new Response(BeerCheckStatus.OK). Otherwise new Response(BeerCheckStatus.NOT_OK). (Show solution)

  • Let’s fix the BeerRestBase class now

    • The idea of CDC is NOT TO TEST every single feature. Contract tests are there to see if the API is matched, NOT that the feature is working. That’s why we shouldn’t be accessing databases etc. That means that we will work with mock of the PersonCheckingService. (Show solution)

    • Let’s annotate the test class with @RunWith(MockitoJUnitRunner.class) to enable Mockito runner.

      @RunWith(MockitoJUnitRunner.class)
      public abstract class BeerRestBase {
      ...
      }
    • We’ll want to test the ProducerController so we can create a field @InjectMocks ProducerController producerController. Mockito will inject any mocks for us via the constructor.

          @Mock PersonCheckingService personCheckingService;
          @InjectMocks ProducerController producerController;
      
          @BeforeEach
          public void setup() {
              given(personCheckingService.shouldGetBeer(argThat(oldEnough()))).willReturn(true);
          }
    • It won’t compile cause we don’t have the oldEnough() method but don’t worry. So this line stubs the shouldGetBeer method in such a way that if the user is old enough then the method will return true. Let’s now add the oldEnoughMethod()

      	private TypeSafeMatcher<PersonToCheck> oldEnough() {
      		return new TypeSafeMatcher<PersonToCheck>() {
      			@Override protected boolean matchesSafely(PersonToCheck personToCheck) {
      				return personToCheck.age >= 20;
      			}
      			@Override public void describeTo(Description description) {
      			}
      		};
      	}
    • We’re using the TypeSafeMatcher from Hamcrest to create a matcher for PersonToCheck. In this case if the person to check is older or is 20 then the method shouldGetBeer method will return true.

    • Now we need to configure RestAssured that is used by Spring Cloud Contract to send requests. In our case we want to profit from MockMvc. In order to set the ProducerController with RestAssured it’s enough to call // https://github.com/spring-cloud/spring-cloud-contract/issues/1428 EncoderConfig encoderConfig = new EncoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false); RestAssuredMockMvc.config = new RestAssuredMockMvcConfig().encoderConfig(encoderConfig); RestAssuredMockMvc.standaloneSetup(producerController);

      @RunWith(MockitoJUnitRunner.class)
      public abstract class BeerRestBase {
      
          @Mock PersonCheckingService personCheckingService;
          @InjectMocks ProducerController producerController;
      
          @BeforeEach
          public void setup() {
              given(personCheckingService.shouldGetBeer(argThat(oldEnough()))).willReturn(true);
      
      		// https://github.com/spring-cloud/spring-cloud-contract/issues/1428
      		EncoderConfig encoderConfig = new EncoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false);
      		RestAssuredMockMvc.config = new RestAssuredMockMvcConfig().encoderConfig(encoderConfig);
      		RestAssuredMockMvc.standaloneSetup(producerController);
          }
      
          private TypeSafeMatcher<PersonToCheck> oldEnough() {
              return new TypeSafeMatcher<PersonToCheck>() {
                  @Override protected boolean matchesSafely(PersonToCheck personToCheck) {
                      return personToCheck.age >= 20;
                  }
                  @Override public void describeTo(Description description) {
                  }
              };
          }
      }
    • With mocks and RestAssured setup - we’re ready to run our HTTP based autogenerated tests

Now you could merge the PR to master, and your CI system would build a fat jar and the stubs.

Congratulations! You’ve completed the producer side of this tutorial.

Consumer flow 2

consumer flow 2
Figure 6. Switch to work online
  • After merging the PR the producer’s stubs reside in some Artifactory / Nexus instance

  • As consumers we no longer want to retrieve the stubs from our local Maven repository - we’d like to download them from the remote location

  • To do that (we won’t do that for the tutorial but you would do it in your "production" code) it’s enough to pass the repositoryRoot parameter and set the stubsMode to StubRunnerProperties.StubsMode.REMOTE

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = WebEnvironment.MOCK)
    @AutoConfigureMockMvc
    @AutoConfigureJsonTesters
    @AutoConfigureStubRunner(
    repositoryRoot="http://www.foo.com/bar,
    ids = "com.example:beer-api-producer:+:stubs:8090",
    stubsMode = StubRunnerProperties.StubsMode.REMOTE
    )
    @DirtiesContext
    public class YourTestOnTheConsumerSide extends AbstractTest {
    }
    • Another option is to pass the property stubrunner.repositoryRoot either as a system / environment property, or via an application.yml and stubrunner.stubs-mode equal to remote

Solutions

Written consumer tests

	@Test
	public void should_give_me_a_beer_when_im_old_enough() throws Exception {
		//remove::start[]
		this.mockMvc.perform(MockMvcRequestBuilders.post("/beer")
				.contentType(MediaType.APPLICATION_JSON)
				.content(this.json.write(new Person("marcin", 22)).getJson()))
				.andExpect(status().isOk())
				.andExpect(content().string("THERE YOU GO"));
		//remove::end[]
	}

	@Test
	public void should_reject_a_beer_when_im_too_young() throws Exception {
		//remove::start[]
		this.mockMvc.perform(MockMvcRequestBuilders.post("/beer")
				.contentType(MediaType.APPLICATION_JSON)
				.content(this.json.write(new Person("marcin", 17)).getJson()))
				.andExpect(status().isOk())
				.andExpect(content().string("GET LOST"));
		//remove::end[]
	}

Adding Spring Cloud Contract Dependency

Maven
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-contract-verifier</artifactId>
	<scope>test</scope>
</dependency>
Gradle
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier")

Proposal of simple contracts by consumer

HTTP communication

Old Enough
// rest/shouldGrantABeerIfOldEnough.groovy
org.springframework.cloud.contract.spec.Contract.make {
		description("""
Represents a successful scenario of getting a beer

```
given:
	client is old enough
when:
	he applies for a beer
then:
	we'll grant him the beer
```

""")
	request {
		method 'POST'
		url '/check'
		body(
				age: 22,
				name: "marcin"
		)
		headers {
			contentType(applicationJson())
		}
	}
	response {
		status 200
		body("""
			{
				"status": "OK"
			}
			""")
		headers {
			contentType(applicationJson())
		}
	}
}
Too Young
// rest/shouldRejectABeerIfTooYoung.groovy
org.springframework.cloud.contract.spec.Contract.make {
		description("""
Represents a successful scenario of getting a beer

```
given:
	client is old enough
when:
	he applies for a beer
then:
	we'll grant him the beer
```

""")
	request {
		method 'POST'
		url '/check'
		body(
				age: 17,
				name: "marcin"
		)
		headers {
			contentType(applicationJson())
		}
	}
	response {
		status 200
		body("""
			{
				"status": "NOT_OK"
			}
			""")
		headers {
			contentType(applicationJson())
		}
	}
}

Messaging communication

Positive Verification
// messaging/shouldSendAcceptedVerification.groovy
org.springframework.cloud.contract.spec.Contract.make {
	description("""
Sends a positive verification message when person is eligible to get the beer

```
given:
	client is old enough
when:
	he applies for a beer
then:
	we'll send a message with a positive verification
```

""")
	// Label by means of which the output message can be triggered
	label 'accepted_verification'
	// output message of the contract
	outputMessage {
		// destination to which the output message will be sent
		sentTo 'verifications'
		// the body of the output message
		body(
            eligible: true
		)
		headers {
			header("contentType", applicationJsonUtf8())
		}
	}
}
Negative Verification
// messaging/shouldSendRejectedVerification.groovy
org.springframework.cloud.contract.spec.Contract.make {
	description("""
Sends a negative verification message when person is not eligible to get the beer

```
given:
	client is too young
when:
	he applies for a beer
then:
	we'll send a message with a negative verification
```

""")
	// Label by means of which the output message can be triggered
	label 'rejected_verification'
	// output message of the contract
	outputMessage {
		// destination to which the output message will be sent
		sentTo 'verifications'
		// the body of the output message
		body(
            eligible: false
		)
		headers {
			header("contentType", applicationJsonUtf8())
		}
	}
}

Missing consumer controller code

		ResponseEntity<Response> response = this.restTemplate.exchange(
				RequestEntity
						.post(URI.create("http://localhost:" + this.port + "/check"))
						.contentType(MediaType.APPLICATION_JSON)
						.body(person),
				Response.class);
		switch (response.getBody().status) {
		case OK:
			return "THERE YOU GO";
		default:
			return "GET LOST";
		}

Stub Logs

2017-05-11 12:16:51.146  INFO 4693 --- [           main] o.s.c.c.s.StubDownloaderBuilderProvider  : Will download stubs using Aether
2017-05-11 12:16:51.148  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Remote repos not passed but the switch to work offline was set. Stubs will be used from your local Maven repository.
2017-05-11 12:16:51.291  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Desired version is [+] - will try to resolve the latest version
2017-05-11 12:16:51.308  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved version is [0.0.1-SNAPSHOT]
2017-05-11 12:16:51.309  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolving artifact [com.example:{producer_artifact}:jar:stubs:0.0.1-SNAPSHOT] using remote repositories []
2017-05-11 12:16:51.317  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved artifact [com.example:{producer_artifact}:jar:stubs:0.0.1-SNAPSHOT] to /home/marcin/.m2/repository/com/example/{producer_artifact}/0.0.1-SNAPSHOT/{producer_artifact}-0.0.1-SNAPSHOT-stubs.jar
2017-05-11 12:16:51.322  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacking stub from JAR [URI: file:/home/marcin/.m2/repository/com/example/{producer_artifact}/0.0.1-SNAPSHOT/{producer_artifact}-0.0.1-SNAPSHOT-stubs.jar]
2017-05-11 12:16:51.327  INFO 4693 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacked file to [/tmp/contracts9053257535983128167]
2017-05-11 12:16:52.608  INFO 4693 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@699e0bf0: startup date [Thu May 11 12:16:52 CEST 2017]; root of context hierarchy
2017-05-11 12:16:52.684  INFO 4693 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2017-05-11 12:16:52.837  INFO 4693 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8090 (http)
2017-05-11 12:16:52.851  INFO 4693 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-05-11 12:16:52.853  INFO 4693 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.14
2017-05-11 12:16:52.975  INFO 4693 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-05-11 12:16:52.975  INFO 4693 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 367 ms
2017-05-11 12:16:52.996  INFO 4693 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'stub' to [/]
2017-05-11 12:16:53.000  INFO 4693 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'admin' to [/__admin/*]
2017-05-11 12:16:53.135  INFO 4693 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
2017-05-11 12:16:53.139  INFO 4693 --- [           main] o.s.c.contract.stubrunner.StubServer     : Started stub server for project [com.example:{producer_artifact}:0.0.1-SNAPSHOT:stubs] on port 8090

Beer Request

class BeerRequest {
	public int age;

	public BeerRequest(int age) {
		this.age = age;
	}

	public BeerRequest() {
	}
}

Missing listener code

		if (verification.eligible) {
			this.eligibleCounter.incrementAndGet();
		} else {
			this.notEligibleCounter.incrementAndGet();
		}

Missing triggers

	@Test public void should_increase_the_eligible_counter_when_verification_was_accepted() throws Exception {
		int initialCounter = this.listener.eligibleCounter.get();

		//remove::start[]
		this.stubTrigger.trigger("accepted_verification");
		//remove::end[]

		then(this.listener.eligibleCounter.get()).isGreaterThan(initialCounter);
	}

	@Test public void should_increase_the_noteligible_counter_when_verification_was_rejected() throws Exception {
		int initialCounter = this.listener.notEligibleCounter.get();

		//remove::start[]
		this.stubTrigger.trigger("rejected_verification");
		//remove::end[]

		then(this.listener.notEligibleCounter.get()).isGreaterThan(initialCounter);
	}

Messaging DSLs

Positive Verification
// messaging/shouldSendAcceptedVerification.groovy
org.springframework.cloud.contract.spec.Contract.make {
	description("""
Sends a positive verification message when person is eligible to get the beer

```
given:
	client is old enough
when:
	he applies for a beer
then:
	we'll send a message with a positive verification
```

""")
	// Label by means of which the output message can be triggered
	label 'accepted_verification'
	// input to the contract
	input {
		// the contract will be triggered by a method
		triggeredBy('clientIsOldEnough()')
	}
	// output message of the contract
	outputMessage {
		// destination to which the output message will be sent
		sentTo 'verifications'
		// the body of the output message
		body(
            eligible: true
		)
		headers {
			header("contentType", applicationJsonUtf8())
		}
	}
}
Negative Verification
// messaging/shouldSendRejectedVerification.groovy
org.springframework.cloud.contract.spec.Contract.make {
	description("""
Sends a negative verification message when person is not eligible to get the beer

```
given:
	client is too young
when:
	he applies for a beer
then:
	we'll send a message with a negative verification
```

""")
	// Label by means of which the output message can be triggered
	label 'rejected_verification'
	// input to the contract
	input {
		// the contract will be triggered by a method
		triggeredBy('clientIsTooYoung()')
	}
	// output message of the contract
	outputMessage {
		// destination to which the output message will be sent
		sentTo 'verifications'
		// the body of the output message
		body(
            eligible: false
		)
		headers {
			header("contentType", applicationJsonUtf8())
		}
	}
}

ProducerController implementation

if (personCheckingService.shouldGetBeer(personToCheck)) {
    return new Response(BeerCheckStatus.OK);
}
return new Response(BeerCheckStatus.NOT_OK);

BeerRestBase

@RunWith(MockitoJUnitRunner.class)
public abstract class BeerRestBase {
	@Mock PersonCheckingService personCheckingService;
	@InjectMocks ProducerController producerController;

	@BeforeEach
	public void setup() {
		given(personCheckingService.shouldGetBeer(argThat(oldEnough()))).willReturn(true);

		// https://github.com/spring-cloud/spring-cloud-contract/issues/1428
		EncoderConfig encoderConfig = new EncoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false);
		RestAssuredMockMvc.config = new RestAssuredMockMvcConfig().encoderConfig(encoderConfig);
		RestAssuredMockMvc.standaloneSetup(producerController);
	}

	private TypeSafeMatcher<PersonToCheck> oldEnough() {
		return new TypeSafeMatcher<PersonToCheck>() {
			@Override protected boolean matchesSafely(PersonToCheck personToCheck) {
				return personToCheck.age >= 20;
			}

			@Override public void describeTo(Description description) {

			}
		};
	}
}

BeerMessagingBase

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProducerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@AutoConfigureMessageVerifier
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
public abstract class BeerMessagingBase {
	@Inject MessageVerifier messaging;
	@Autowired PersonCheckingService personCheckingService;

	@BeforeEach
	public void setup() {
		// let's clear any remaining messages
		// output == destination or channel name
		this.messaging.receive("output", 100, TimeUnit.MILLISECONDS);
	}

	public void clientIsOldEnough() {
		personCheckingService.shouldGetBeer(new PersonToCheck(25));
	}

	public void clientIsTooYoung() {
		personCheckingService.shouldGetBeer(new PersonToCheck(5));
	}
}

Messaging implementation

		boolean shouldGetBeer = personToCheck.age >= 20;
		this.source.send("output-out-0", new Verification(shouldGetBeer));
		return shouldGetBeer;

Missing Consumer Controller Code with Discovery

		ResponseEntity<Response> response = this.restTemplate.exchange(
				RequestEntity
						.post(URI.create("http://somenameforproducer/check"))
						.contentType(MediaType.APPLICATION_JSON)
						.body(person),
				Response.class);
		switch (response.getBody().status) {
		case OK:
			return "THERE YOU GO";
		default:
			return "GET LOST";
		}

Back to the Main Page