This version is still in development and is not considered stable yet. For the latest stable version, please use spring-cloud-contract 4.1.4! |
Using the Stub Runner Boot Application
Spring Cloud Contract Stub Runner Boot is a Spring Boot application that exposes REST endpoints to trigger the messaging labels and to access WireMock servers.
Stub Runner Boot Security
The Stub Runner Boot application is not secured by design - securing it would require to add security to all stubs even if they don’t actually require it. Since this is a testing utility - the server is not intended to be used in production environments.
It is expected that only a trusted client has access to the Stub Runner Boot server. You should not run this application as a Fat Jar or a Docker Image in untrusted locations. |
Stub Runner Server
To use the Stub Runner Server, add the following dependency:
compile "org.springframework.cloud:spring-cloud-starter-stub-runner"
Then annotate a class with @EnableStubRunnerServer
, build a fat jar, and it is ready to work.
For the properties, see the Stub Runner Spring section.
Stub Runner Server Fat Jar
You can download a standalone JAR from Maven (for example, for version 2.0.1.RELEASE) by running the following commands:
$ wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.0.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.0.1.RELEASE.jar'
$ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=...
Spring Cloud CLI
Starting from the 1.4.0.RELEASE
version of the Spring Cloud CLI
project, you can start Stub Runner Boot by running spring cloud stubrunner
.
To pass the configuration, you can create a stubrunner.yml
file in the current working directory,
in a subdirectory called config
, or in ~/.spring-cloud
. The file could resemble the following
example for running stubs installed locally:
stubrunner:
stubsMode: LOCAL
ids:
- com.example:beer-api-producer:+:9876
Then you can call spring cloud stubrunner
from your terminal window to start
the Stub Runner server. It is available at port 8750
.
Endpoints
Stub Runner Boot offers two endpoints:
HTTP
For HTTP, Stub Runner Boot makes the following endpoints available:
-
GET
/stubs
: Returns a list of all running stubs inivy:integer
notation -
GET
/stubs/{ivy}
: Returns a port for the givenivy
notation (when calling the endpointivy
can also beartifactId
only)
Messaging
For Messaging, Stub Runner Boot makes the following endpoints available:
-
GET
/triggers
: Returns a list of all running labels inivy : [ label1, label2 …]
notation -
POST
/triggers/{label}
: Runs a trigger withlabel
-
POST
/triggers/{ivy}/{label}
: Runs a trigger with alabel
for the givenivy
notation (when calling the endpoint,ivy
can also beartifactId
only)
Example
The following example shows typical usage of Stub Runner Boot:
@SpringBootTest(classes = StubRunnerBoot, properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec {
@Autowired
StubRunning stubRunning
@BeforeEach
void setup() {
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
new TriggerController(stubRunning))
}
@Test
void 'should return a list of running stub servers in "full ivy port" notation'() {
when:
String response = RestAssuredMockMvc.get('/stubs').body.asString()
then:
def root = new JsonSlurper().parseText(response)
assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer
}
@Test
void 'should return a port on which a #stubId stub is running'() {
given:
def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:+',
'org.springframework.cloud.contract.verifier.stubs:bootService',
'bootService']
stubIds.each {
when:
def response = RestAssuredMockMvc.get("/stubs/${it}")
then:
assert response.statusCode == 200
assert Integer.valueOf(response.body.asString()) > 0
}
}
@Test
void 'should return 404 when missing stub was called'() {
when:
def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
then:
assert response.statusCode == 404
}
@Test
void 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
when:
String response = RestAssuredMockMvc.get('/triggers').body.asString()
then:
def root = new JsonSlurper().parseText(response)
assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["return_book_1"])
}
@Test
void 'should trigger a messaging label'() {
given:
StubRunning stubRunning = Mockito.mock(StubRunning)
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
when:
def response = RestAssuredMockMvc.post("/triggers/delete_book")
then:
response.statusCode == 200
and:
Mockito.verify(stubRunning).trigger('delete_book')
}
@Test
void 'should trigger a messaging label for a stub with #stubId ivy notation'() {
given:
StubRunning stubRunning = Mockito.mock(StubRunning)
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
and:
def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
stubIds.each {
when:
def response = RestAssuredMockMvc.post("/triggers/$it/delete_book")
then:
assert response.statusCode == 200
and:
Mockito.verify(stubRunning).trigger(it, 'delete_book')
}
}
@Test
void 'should throw exception when trigger is missing'() {
when:
BDDAssertions.thenThrownBy(() -> RestAssuredMockMvc.post("/triggers/missing_label"))
.hasMessageContaining("Exception occurred while trying to return [missing_label] label.")
.hasMessageContaining("Available labels are")
.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
}
}
Stub Runner Boot with Service Discovery
One way to use Stub Runner Boot is to use it as a feed of stubs for “smoke tests”. What does that mean? Assume that you do not want to deploy 50 microservices to a test environment in order to see whether your application works. You have already run a suite of tests during the build process, but you would also like to ensure that the packaging of your application works. You can deploy your application to an environment, start it, and run a couple of tests on it to see whether it works. We can call those tests “smoke tests”, because their purpose is to check only a handful of testing scenarios.
The problem with this approach is that, if you use microservices, you most likely also use a service discovery tool. Stub Runner Boot lets you solve this issue by starting the required stubs and registering them in a service discovery tool.
Now assume that we want to start this application so that the stubs get automatically registered.
We can do so by running the application with java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar
, where
${SYSTEM_PROPS}
.
That way, your deployed application can send requests to started WireMock servers through service
discovery. Most likely, points 1 through 3 could be set by default in application.yml
, because they are not
likely to change. That way, you can provide only the list of stubs to download whenever you start
the Stub Runner Boot.