Stub Runner

One of the issues that you could have encountered while using Accurest was to pass the generated WireMock JSON stubs from the server side to the client side (or various clients). The same takes place in terms of client side generation for messaging.

Copying the JSON files / setting the client side for messaging manually is out of the question.

Publishing stubs as JARs

The easiest approach would be to centralize the way stubs are kept. For example you can keep them as JARs in a Maven repository.

Gradle

Example of Accurest Gradle setup:

ext {
        contractsDir = file("mappings")
        stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
        wireMockStubsOutputDir = file(new File(stubsOutputDirRoot, 'repository/mappings/'))
        contractsOutputDir = file(new File(stubsOutputDirRoot, 'repository/accurest/'))
}

task copyContracts(type: Copy) {
        from contractsDir
        include '**/*.groovy'
        into contractsOutputDir
}

task stubsJar(type: Jar, dependsOn: ["generateWireMockClientStubs", copyContracts]) {
        baseName = "${project.name}"
        classifier = "stubs"
        from stubsOutputDirRoot
}

artifacts {
        archives stubsJar
}

publishing {
        publications {
                stubs(MavenPublication) {
                        artifactId "${project.name}-stubs"
                        artifact stubsJar
                }
        }
}

Maven

Example of Maven can be found in the Accurest Maven Plugin README

Modules

Accurest comes with a new structure of modules

└── stub-runner
    ├── stub-runner
    ├── stub-runner-boot
    ├── stub-runner-junit
    ├── stub-runner-spring
    └── stub-runner-spring-cloud

Stub Runner Core

Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of Consumer Driven Contracts.

Stub Runner allows you to automatically download the stubs of the provided dependencies, start WireMock servers for them and feed them with proper stub definitions. For messaging, special stub routes are defined.

Running stubs

Running using main app

You can set the following options to the main class:

-maxp (--maxPort) N            : Maximum port value to be assigned to the
                                 Wiremock instance. Defaults to 15000
                                 (default: 15000)
-minp (--minPort) N            : Minimal port value to be assigned to the
                                 Wiremock instance. Defaults to 10000
                                 (default: 10000)
-s (--stubs) VAL               : Comma separated list of Ivy representation of
                                 jars with stubs. Eg. groupid:artifactid1,group
                                 id2:artifactid2:version:classifier
-sr (--stubRepositoryRoot) VAL : Location of a Jar containing server where you
                                 keep your stubs (e.g. http://nexus.net/content
                                 /repositories/repository)
-ss (--stubsSuffix) VAL        : Suffix for the jar containing stubs (e.g.
                                 'stubs' if the stub jar would have a 'stubs'
                                 classifier for stubs: foobar-stubs ).
                                 Defaults to 'stubs' (default: stubs)
-wo (--workOffline)            : Switch to work offline. Defaults to 'false'
                                 (default: false)
Building a Fat Jar

Just call the following command:

./gradlew stub-runner-root:stub-runner:shadowJar -PfatJar

and inside the build/lib there will be a Fat Jar with classifier fatJar waiting for you to execute. E.g.

java -jar stub-runner/stub-runner/build/libs/stub-runner-1.0.1-SNAPSHOT-fatJar.jar -sr http://a.b.com -s a:b:c,d:e,f:g:h:i

Stub runner configuration

You can configure the stub runner by either passing the full arguments list with the -Pargs like this:

./gradlew stub-runner-root:stub-runner:run -Pargs="-c pl -minp 10000 -maxp 10005 -s a:b:c,d:e,f:g:h"

or each parameter separately with a -P prefix and without the hyphen - in the name of the param

./gradlew stub-runner-root:stub-runner:run -Pc=pl -Pminp=10000 -Pmaxp=10005 -Ps=a:b:c,d:e,f:g:h
HTTP Stubs

Stubs are defined in JSON documents, whose syntax is defined in WireMock documentation

Example:

{
    "request": {
        "method": "GET",
        "url": "/ping"
    },
    "response": {
        "status": 200,
        "body": "pong",
        "headers": {
            "Content-Type": "text/plain"
        }
    }
}
Viewing registered mappings

Every stubbed collaborator exposes list of defined mappings under __/admin/ endpoint.

Messaging Stubs

Depending on the provided Stub Runner dependency and the DSL the messaging routes are automatically set up.

Stub Runner Boot

Feature available since {messaging_version}

Accurest Stub Runner Boot is a Spring Boot application that exposes REST endpoints to trigger the messaging labels and to access started WireMock servers.

One of the usecases is to run some smoke (end to end) tests on a deployed application. You can read more about this in the "Microservice Deployment" article at Too Much Coding blog.

How to use it?

Just add the

compile "io.codearte.accurest:stub-runner-boot:${accurestVersion}"

and a messaging implementation:

// for Apache Camel
compile "io.codearte.accurest:stub-runner-messaging-camel:${accurestVersion}"
// for Spring Integration
compile "io.codearte.accurest:stub-runner-messaging-integration:${accurestVersion}"
// for Spring Cloud Stream
compile "io.codearte.accurest:stub-runner-messaging-stream:${accurestVersion}"

Build a fat-jar and you’re ready to go!

For the properties check the Stub Runner Spring section.

Endpoints

HTTP
  • GET /stubs - returns a list of all running stubs in ivy:integer notation

  • GET /stubs/{ivy} - returns a port for the given ivy notation (when calling the endpoint ivy can also be artifactId only)

Messaging

For Messaging

  • GET /triggers - returns a list of all running labels in ivy : [ label1, label2 …​] notation

  • POST /triggers/{label} - executes a trigger with label

  • POST /triggers/{ivy}/{label} - executes a trigger with label for the given ivy notation (when calling the endpoint ivy can also be artifactId only)

Example

@ContextConfiguration(classes = [StubRunnerBootSpec, StubRunnerBoot], loader = SpringApplicationContextLoader)
@EnableBinding
@Configuration
class StubRunnerBootSpec extends Specification {

        @Autowired StubRunning stubRunning

        def setup() {
                RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
                                new TriggerController(stubRunning))
        }

        def '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)
                        root.'io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs' instanceof Integer
        }

        def 'should return a port on which a [#stubId] stub is running'() {
                when:
                        def response = RestAssuredMockMvc.get("/stubs/${stubId}")
                then:
                        response.statusCode == 200
                        response.body.as(Integer) > 0
                where:
                        stubId << ['io.codearte.accurest.stubs:streamService:+:stubs',
                                           'io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs',
                                           'io.codearte.accurest.stubs:streamService:+',
                                           'io.codearte.accurest.stubs:streamService',
                                           'streamService']
        }

        def 'should return 404 when missing stub was called'() {
                when:
                        def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
                then:
                        response.statusCode == 404
        }

        def '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)
                        root.'io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"])
        }

        def 'should trigger a messaging label'() {
                given:
                        StubRunning stubRunning = Mock()
                        RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
                when:
                        def response = RestAssuredMockMvc.post("/triggers/delete_book")
                then:
                        response.statusCode == 200
                and:
                        1 * stubRunning.trigger('delete_book')
        }

        def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() {
                given:
                        StubRunning stubRunning = Mock()
                        RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
                when:
                        def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book")
                then:
                        response.statusCode == 200
                and:
                        1 * stubRunning.trigger(stubId, 'delete_book')
                where:
                        stubId << ['io.codearte.accurest.stubs:streamService:stubs', 'io.codearte.accurest.stubs:streamService', 'streamService']
        }

        def 'should return when trigger is missing'() {
                when:
                        def response = RestAssuredMockMvc.post("/triggers/missing_label")
                then:
                        response.statusCode == 404
                        def root = new JsonSlurper().parseText(response.body.asString())
                        root.'io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"])
        }

}

Stub Runner JUnit Rule

Stub Runner comes with a JUnit rule thanks to which you can very easily download and run stubs for given group and artifact id:

@ClassRule public static AccurestRule rule = new AccurestRule()
                .repoRoot(repoRoot())
                .downloadStub("io.codearte.accurest.stubs", "loanIssuance")
                .downloadStub("io.codearte.accurest.stubs:fraudDetectionServer");

After that rule gets executed Stub Runner connects to your Maven repository and for the given list of dependencies tries to:

  • download them

  • cache them locally

  • unzip them to a temporary folder

  • start a WireMock server for each Maven dependency on a random port from the provided range of ports / provided port

  • feed the WireMock server with all JSON files that are valid WireMock definitions

Stub Runner uses Eclipse Aether mechanism to download the Maven dependencies. Check their docs for more information.

Since the AccurestRule implements the StubFinder it allows you to find the started stubs:

package io.codearte.accurest.stubrunner

import io.codearte.accurest.dsl.GroovyDsl

interface StubFinder extends StubTrigger {
        /**
         * For the given groupId and artifactId tries to find the matching
         * URL of the running stub.
         *
         * @param groupId - might be null. In that case a search only via artifactId takes place
         * @return URL of a running stub or null if not found
         */
        URL findStubUrl(String groupId, String artifactId)

        /**
         * For the given Ivy notation {@code groupId:artifactId} tries to find the matching
         * URL of the running stub. You can also pass only {@code artifactId}.
         *
         * @param ivyNotation - Ivy representation of the Maven artifact
         * @return URL of a running stub or null if not found
         */
        URL findStubUrl(String ivyNotation)

        /**
         * Returns all running stubs
         */
        RunningStubs findAllRunningStubs()

        /**
         * Returns the list of Accurest contracts
         */
        Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts()
}

Example of usage in Spock tests:

@ClassRule @Shared AccurestRule rule = new AccurestRule()
                .repoRoot(AccurestRuleSpec.getResource("/m2repo").toURI().toString())
                .downloadStub("io.codearte.accurest.stubs", "loanIssuance")
                .downloadStub("io.codearte.accurest.stubs:fraudDetectionServer")

def 'should start WireMock servers'() {
        expect: 'WireMocks are running'
                rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
                rule.findStubUrl('loanIssuance') != null
                rule.findStubUrl('loanIssuance') == rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
                rule.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
        and:
                rule.findAllRunningStubs().isPresent('loanIssuance')
                rule.findAllRunningStubs().isPresent('io.codearte.accurest.stubs', 'fraudDetectionServer')
                rule.findAllRunningStubs().isPresent('io.codearte.accurest.stubs:fraudDetectionServer')
        and: 'Stubs were registered'
                "${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
                "${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
}

Example of usage in JUnit tests:

@Test
public void should_start_wiremock_servers() throws Exception {
        // expect: 'WireMocks are running'
                then(rule.findStubUrl("io.codearte.accurest.stubs", "loanIssuance")).isNotNull();
                then(rule.findStubUrl("loanIssuance")).isNotNull();
                then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl("io.codearte.accurest.stubs", "loanIssuance"));
                then(rule.findStubUrl("io.codearte.accurest.stubs:fraudDetectionServer")).isNotNull();
        // and:
                then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue();
                then(rule.findAllRunningStubs().isPresent("io.codearte.accurest.stubs", "fraudDetectionServer")).isTrue();
                then(rule.findAllRunningStubs().isPresent("io.codearte.accurest.stubs:fraudDetectionServer")).isTrue();
        // and: 'Stubs were registered'
                then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance");
                then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer");
}

Check the Common properties for JUnit and Spring for more information on how to apply global configuration of Stub Runner.

Providing fixed ports

You can also run your stubs on fixed ports. You can do it in two different ways. One is to pass it in the properties, and the other via fluent API of JUnit rule.

Fluent API

When using the AccurestRule you can add a stub to download and then pass the port for the last downloaded stub.

@ClassRule public static AccurestRule rule = new AccurestRule()
                .repoRoot(repoRoot())
                .downloadStub("io.codearte.accurest.stubs", "loanIssuance")
                .withPort(12345)
                .downloadStub("io.codearte.accurest.stubs:fraudDetectionServer:12346");

You can see that for this example the following test is valid:

then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL());
then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL());

Stub Runner Spring

Sets up Spring configuration of the Stub Runner project.

By providing a list of stubs inside your configuration file the Stub Runner automatically downloads and registers in WireMock the selected stubs.

If you want to find the URL of your stubbed dependency you can autowire the StubFinder interface and use its methods as presented below:

@ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader)
class StubRunnerConfigurationSpec extends Specification {

        @Autowired StubFinder stubFinder

        def 'should start WireMock servers'() {
                expect: 'WireMocks are running'
                        stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
                        stubFinder.findStubUrl('loanIssuance') != null
                        stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
                        stubFinder.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
                and:
                        stubFinder.findAllRunningStubs().isPresent('loanIssuance')
                        stubFinder.findAllRunningStubs().isPresent('io.codearte.accurest.stubs', 'fraudDetectionServer')
                        stubFinder.findAllRunningStubs().isPresent('io.codearte.accurest.stubs:fraudDetectionServer')
                and: 'Stubs were registered'
                        "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
                        "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
        }

        @Configuration
        @Import(StubRunnerConfiguration)
        @EnableAutoConfiguration
        static class Config {}
}

for the following configuration file:

stubrunner.stubs.repository.root: classpath:m2repo/repository/
stubrunner.stubs.ids: io.codearte.accurest.stubs:loanIssuance,io.codearte.accurest.stubs:fraudDetectionServer

Stub Runner Spring Cloud

Registers the stubs in the provided Service Discovery. It’s enough to add the jar

io.codearte.accurest:stub-runner-spring-cloud

and the Stub Runner autoconfiguration should be picked up.

Stubbing Service Discovery

The most important feature of Stub Runner Spring Cloud is the fact that it’s stubbing

  • DiscoveryClient

  • Ribbon ServerList

that means that regardles of the fact whether you’re using Zookeeper, Consul, Eureka or anything else, you don’t need that in your tests. We’re starting WireMock instances of your dependencies and we’re telling your application whenever you’re using Feign, load balanced RestTemplate or DiscoveryClient directly, to call those stubbed servers instead of calling the real Service Discovery tool.

Additional Configuration

You can match the artifactId of the stub with the name of your app by using the stubrunner.stubs.idsToServiceIds: map. You can disable Stub Runner Ribbon support by providing: stubrunner.cloud.ribbon.enabled equal to false You can disable Stub Runner support by providing: stubrunner.cloud.enabled equal to false

Common properties for JUnit and Spring

Some of the properties that are repetitive can be set using system properties or property sources (for Spring). Here are their names with their default values:

Property name Default value Description

stubrunner.port.range.min

10000

Minimal value of a port for a started WireMock with stubs

stubrunner.port.range.max

15000

Minimal value of a port for a started WireMock with stubs

stubrunner.stubs.repository.root

Comma separated list of Maven repo urls. If blank then will call the local maven repo

stubrunner.stubs.classifier

stubs

Default classifier for the stub artifacts

stubrunner.work-offline

false

If true then will not contact any remote repositories to download stubs

stubrunner.stubs.ids

Comma separated list of Ivy notation of stubs to download

Stub runner stubs ids

You can provide the stubs to download via the stubrunner.stubs.ids system property. They follow the following pattern:

groupId:artifactId:version:classifier:port

version, classifier and port are optional.

  • If you don’t provide the port then a random one will be picked

  • If you don’t provide the classifier then the default one will be taken.

  • If you don’t provide the version then the + will be passed and the latest one will be downloaded

Where port means the port of the WireMock server.