Sends an HTTP request to fraud detection from a test Gets a Stream message from fraud detection
web, stubrunner, stream rabbit, wiremock, Eureka Discovery
and stream-test-support for Contract & Stream
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>web, stream rabbit, verifier, REST Docs, wiremock, Eureka Discovery
and stream-test-support for Contract & Stream
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>and plugin - REMEMBER ABOUT EXTENSIONS
<!-- ADD A PLUGIN -->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<!-- REMEMBER ABOUT THIS LINE -->
<extensions>true</extensions>
<configuration>
<baseClassForTests>com.example.frauddetection.BaseClass</baseClassForTests>
</configuration>
</plugin>CONSUMER
-
Generate consumer from start.spring.io
-
Add the missing dependencies
-
Start with a test on the consumer side
-
Write a not passing test to reach the
/fraudendpoint of the producer -
Add the WireMock stub to make the test pass (port
6543)
PRODUCER
-
Generate producer from start.spring.io
-
Add the missing dependencies
-
Write a contract for the
/fraudsendpoint (the typo is deliberate) -
Configure the plugin with
baseClassForTests -
Run
./mvnw clean install- show breaking tests -
Write the controller
-
Write missing base class setup
-
Rerun the
./mvnw clean install- things pass -
Run the app at port
6544
CONSUMER
-
Write a new test that will reach the producer application directly (port
6544) -
The previous test with a stub passes, the new one fails (oops)
-
Write a new test with
StubRunnerRulecom.example:fraud-detectionat (port6545) -
The test fails cause first we’ll shoot at
/fraud -
The test passes once we shoot at
/frauds
CONSUMER
-
Enable binding for Sink
-
In
application.properties-
set the port to
9876 -
spring.cloud.stream.bindings.input.destination=fraud -
on the producer side it will be
frauds
-
-
Add a
Fraudpojo with aname -
Add a
FraudListener@Component-
with a method
fraudthat has@StreamListener(Sink.class) -
let it print a message out
-
and store the name
-
-
Let’s write a
FraudTestsclass-
we want to see if our listener will work fine if we send it our POJO
-
let’s write a test
should_store_info_about_fraud -
make it a SpringBoot test
-
@Autowired FraudListener -
@Autowired Sink -
given: a
new Fraud("marcin") -
when:
sink.input().send(MessageBuilder.withPayload(fraud).build()); -
then:
fraudListener.name == "marcin"
-
-
The test passes - let’s go to the producer
PRODUCER
-
EnableBinding(Source.class) -
Set properties
-
spring.cloud.stream.bindings.output.destination=frauds -
Yup, that’s a typo over there ^^
-
spring.cloud.stream.bindings.output.contentType=application/json -
server.port=6544
-
-
Let’s write a contract for messaging
-
label
trigger_a_fraud -
input method
triggerMethod() -
output to destination
frauds -
body
surname: "Long"
-
-
Create a
Fraudpojo in theFraudController -
FraudControllerwill need a@PostMapping("/message")method calledmessagethat will useSourceto send a message withnew Fraud("Long") -
Let’s run
./mvnw clean installand generate tests-
they will fail cause we have a missing
triggerMethod()
-
-
Let’s create the
triggerMethod()in theBaseClass-
Also we need to add the Spring context with
@AutoConfigureMessageVerifier -
@Autowired FraudController -
call in the
triggerMethod()thefraudController.message()
-
Let’s try to make both apps work! Let’s run them together
curl -X POST http://localhost:6544/messageNothing happens… Even though the tests passed. That’s for 2 reasons
-
the destination is wrong. One is sending to
fraudsthe other listening tofraud -
the POJO is wrong. Once expects
namethe othersurname
Time to fix the consumer
CONSUMER
-
Let’s use Stub Runner
-
@AutoConfigureStubRunner(workOffline = true, ids = "com.example:fraud-detection") -
@Autowired StubTrigger -
stubTrigger.trigger("trigger_a_fraud");-
the test won’t pass - let’s update the destination
-
-
spring.cloud.stream.bindings.input.destination=frauds-
let’s run again the tests - still they don’t pass cause the name is wrong
-
-
let’s change
Fraudto usesurname-
now if we rerun the tests they pass
-
Let’s run both apps again, send the CURL - now they should work
PRODUCER
-
We need to write a test for the
messageendpoint -
We’ll write a test called
FraudControllerTests -
Use the test slices
AutoConfigureMockMvcand@AutoConfigureRestDocs(outputDir = "target/snippets")@RunWith(SpringRunner.class) @SpringBootTest(classes = FraudDetectionApplication.class) @AutoConfigureRestDocs(outputDir = "target/snippets") @AutoConfigureMockMvc
-
Write a simple test to see if status OK happens when you send a POST to
/message/@Autowired private MockMvc mockMvc; @Test public void should_accept_a_post_message() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/message")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcRestDocumentation.document("message")); } -
Time to configure the build to package stuff properly
-
We want to disable the default Spring Cloud Contract packaging approach
-
You can do it by setting
<spring.cloud.contract.verifier.jar.skip>true</spring.cloud.contract.verifier.jar.skip>property
-
-
Add the
src/assembly/stub.xml<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 https://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>stubs</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.build.directory}/snippets/stubs</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/stubs/META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <fileSet> <directory>${basedir}/src/test/resources/contracts</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory> <includes> <include>**/*.groovy</include> </includes> </fileSet> </fileSets> </assembly>
-
Add the assembly plugin setup
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>stub</id> <phase>prepare-package</phase> <goals> <goal>single</goal> </goals> <inherited>false</inherited> <configuration> <attach>true</attach> <descriptor>${basedir}/src/assembly/stub.xml</descriptor> </configuration> </execution> </executions> </plugin>
-
Run
./mvnw clean install
CONSUMER
-
Reuse the previously created setup and just send a
postmethod to/messages. Expect status200
CONSUMER
-
Add
@EnableDiscoveryClient -
Add
@Bean @LoadBalancedforRestTemplate -
Add
spring.application.name=car-rental -
In
FraudTestswe’ll add another test (we need@AutoConfigureStubRunner)-
@Autowired RestTemplate -
Use the
RestTemplateto call"http://fraud-detection/frauds" -
The test passes cause SC-Contract redirects the calls via artifact id
-
-
Clone Stub Runner for Eureka & Rabbit from https://github.com/spring-cloud-samples/github-analytics-stub-runner-boot
-
Build it locally
-
java -jar target/github-analytics-stub-runner-boot-0.0.1.M1.jar --stubrunner.workoffline=true --stubrunner.ids=com.example:fraud-detection -
Show http://localhost:8761 with registered apps
-
Run
car-rental -
curl -X POST http://localhost:8083/triggers/trigger_a_fraud -
Check that in the logs we see that a message was received
-
Write a test that isn’t annotated with
@AutoConfigureStubRunnerand injectRestTemplate- you can shoot a request tofraud-detectionand a stub will respond-
or write a POST endpoint to
/rentthat will receive a{"name":"marcin"}and then it will callfraud-detection/fraudsto retrieve a list of frauds. If that string contains thenamethen set status code406and textNO. Otherwise200andyesand run a curlcurl -X POST -d '{"name":"marcin"}' http://localhost:8765
-
-
Install
npm -
Install the
requestmodulenpm install request -
Ensure that Stub Runner Boot is not running
-
Go to
nodejsfolder and execute$ node app.js -
You will get sth like this
ERROR - status [404] -
Next run stub runner boot and show how easy it is to start a stub (this assumes that
fraud-detectionstubs were installed locally)$ mkdir -p target $ wget -O target/stub-runner.jar 'https://search.maven.org/remote_content?g=org.springframework.cloud&a=spring-cloud-contract-stub-runner-boot&v=1.2.3.RELEASE' $ java -jar target/stub-runner.jar --stubrunner.workOffline=true --stubrunner.ids="com.example:fraud-detection:+:9876"
or use docker (we’re mounting your local .m2 to the docker’s .m2)
#!/bin/bash
# Provide the Spring Cloud Contract Docker version
SC_CONTRACT_DOCKER_VERSION="1.2.4.BUILD-SNAPSHOT"
# Spring Cloud Contract Stub Runner properties
STUBRUNNER_PORT="8083"
# Stub coordinates 'groupId:artifactId:version:classifier:port'
STUBRUNNER_IDS="com.example:fraud-detection:0.0.1-SNAPSHOT:stubs:9876"
# Run the docker with Stub Runner Boot
docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" -e "STUBRUNNER_WORK_OFFLINE=true" -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" -p "9876:9876" -v "${HOME}/.m2/:/root/.m2:ro" springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"or use Spring Cloud CLI
$ spring cloud stubrunner-
You will get sth like this
["marcin","josh"]