
This recipe introduce an example which do async messaging contract test by Spring Cloud Contract. There are two microservices in this example, EquipmentActivityService
and EquipmentCmdService
. EquipmentActivityService
send EquipmentInfo
to EquipmentCmdService
by Spring Cloud Stream and Kafka. Below is class for event payload.
@Datapublic class EquipmentInfo {private String equipmentId;private String equipmentSize;private List<RtiEvent> events;}@Datapublic class RtiEvent {private String operation;private String eventId;private String status;}
In this example, EquipmentActivityService
is messaging producer and EquipmentCmdService
is messaging consummer. We can define an constract and do contract test for produer and consummer separately. If both sides pass the contract test, then we think these two mircoservices can interact with async messaging successfully.
Let us start to walk through the contract test on both sides step by step.

Producer Side
1. include spring-cloud-starter-contract-verifier
dependency in pom.xml
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-contract-verifier</artifactId><scope>test</scope></dependency>
2. include spring-cloud-contract-maven-plugin
maven plugin in pom.xml
You also need configure baseClassMappings
to define Base Class for auto-created test class in produer side. You can configure here at first and implement the Base Class in the following step.
<plugin><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-contract-maven-plugin</artifactId><version>${spring-cloud-contract.version}</version><extensions>true</extensions><configuration><baseClassMappings><baseClassMapping><contractPackageRegex>.*messaging.*</contractPackageRegex><baseClassFQN>.rti.equipmentactivity.MessagingContractBase</baseClassFQN></baseClassMapping></baseClassMappings></configuration></plugin>
3. write contract in groovy format
You can create sub folder in $rootDir/src/test/resources/contracts
, and then put the contract file into it.
import org.springframework.cloud.contract.spec.ContractContract.make {label 'triggerEquipmentInfoSendOutLabel' //1input {triggeredBy('triggerEquipmentInfoSendOut()') //2}outputMessage {sentTo( $(consumer('input'), producer('output')) ) //3body([ //4equipmentId : $(producer(regex('[A-Z]{4}[0-9]{6}'))),equipmentSize: "20GP",events : [[operation: 'EXPORT',eventId : $(producer(regex('[A-Z]{4}[0-9]{8}'))),status : 'OK'], [operation: 'INPUT_PROCESSING',eventId : $(producer(regex('[A-Z]{4}[0-9]{8}'))),status : 'FAILED']]])headers {header('kafka_messageKey', $(producer(regex('[A-Z]{4}[0-9]{6}'))))messagingContentType(applicationJson())}}}
Notes:
contract label, which can be used to trigger sending event from stub when do test in consummer side.
input trigger for produer side auto-created test codes, baseClassForTests need to implement it.
For producer side, event will be sent to
output
channel. For consummer side, event will come frominput
channel.event payload body. Field value can use String or Regex for match.
4. implement MessagingContractBase
base class for tests
package com.yyssky.rti.equipmentactivity;@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)@AutoConfigureMessageVerifierpublic abstract class MessagingContractBase {//@Autowired//private MessageVerifier verifier;public void triggerEquipmentInfoSendOut() {}}
run mvn clean install
, test will be failed since triggerEquipmentInfoSendOut() do nothing, codes for messaging outgoing is not implemented.
5. implement messaging outgoing codes
You need to add a class and method in it to send out event as below.
@Service@EnableBinding({Processor.class})public class EquipmentActivityService {@Autowiredprivate Processor processor;public void sendEquipmentInfo() {RtiEvent event1 = RtiEvent.builder().eventId("ASDF12345678").operation("EXPORT").status("OK").build();RtiEvent event2 = RtiEvent.builder().eventId("QWER09876543").operation("INPUT_PROCESSING").status("FAILED").build();List<RtiEvent> list = new ArrayList<>();list.add(event1);list.add(event2);EquipmentInfo equipmentInfo = EquipmentInfo.builder().equipmentId("ZXCV567890").equipmentSize("20GP").events(list).build();Message<EquipmentInfo> message = MessageBuilder.withPayload(equipmentInfo).setHeader(KafkaHeaders.MESSAGE_KEY, equipmentInfo.getEquipmentId()).build();processor.output().send(message);}}
Then you need to call the method in triggerEquipmentInfoSendOut() in the base test class.
package com.yyssky.rti.equipmentactivity;@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)@AutoConfigureMessageVerifierpublic abstract class MessagingContractBase {@Autowiredprivate EquipmentActivityService equipmentActivityService;public void triggerEquipmentInfoSendOut() {equipmentActivityService.sendEquipmentInfo();}}
run mvn clean install
again and the test should be past. You also can check stub jar is created and installed in local m2 repo.
Now produer side has passed the contract test and also stub jar for consummer side is installed, we can start to do contract test in consummer side now.

Consummer Side
1. include spring-cloud-starter-contract-stub-runner
dependency in pom.xml
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-contract-stub-runner</artifactId><scope>test</scope></dependency>
2. include Messaging Producer's stub dependency in pom.xml
<dependency><groupId>com.yyssky.rti</groupId><artifactId>equipment-activity</artifactId><classifier>stubs</classifier><version>0.0.1-SNAPSHOT</version><scope>test</scope><exclusions><exclusion><groupId>*</groupId><artifactId>*</artifactId></exclusion></exclusions></dependency>
3. write contract test class
@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)@AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.LOCAL, ids = "com.yyssky.rti:equipment-activity")public class EquipmentCmdListenerTests {@Autowiredprivate StubTrigger stubTrigger;@Autowiredprivate EquipmentCmdListener equipmentCmdListener;@Testpublic void shouldReceiveEquipmentInfo() {Assertions.assertThat(equipmentCmdListener.getReceivedEvents()).hasSize(0);stubTrigger.trigger("triggerEquipmentInfoSendOutLabel");Assertions.assertThat(equipmentCmdListener.getReceivedEvents()).hasSize(1);}}
In the test method, we use StubTrigger
to trigger stub send out event about the contract label.
run mvn clean test
it will be fail, since we have not implemented event listener in EquipmentCmdListener class. Let us do it.
4. implement EquipmentCmdListener
class to pass the test
@Component@EnableBinding({Processor.class})@Slf4jpublic class EquipmentCmdListener {private List<EquipmentInfo> receivedEvents = new ArrayList<>();@StreamListener(Processor.INPUT)public void onEvent(@Payload EquipmentInfo equipmentInfo){log.info("************* receive event, equipmentInfo: {}", equipmentInfo.toString());receivedEvents.add(equipmentInfo); //just for test}public List<EquipmentInfo> getReceivedEvents() {return receivedEvents;}}
In the EquipmentCmdListener class, the List is just for test.
run mvn clean test
again, consummer side contract test will be pass.

END






