From 7d293670f7cf0a48d4e6430c9fc9aee4838f8dbe Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 12 Jul 2016 16:54:30 +0200 Subject: [PATCH] Removed the deprecated documentation --- .../main/asciidoc/deprecated/contract.html | 1232 ------ .../main/asciidoc/deprecated/images/Deps.png | Bin 37192 -> 0 bytes .../asciidoc/deprecated/images/Stubs1.png | Bin 35170 -> 0 bytes .../asciidoc/deprecated/images/Stubs2.png | Bin 18143 -> 0 bytes docs/src/main/asciidoc/deprecated/index.html | 3914 ----------------- .../asciidoc/deprecated/introduction.html | 797 ---- .../main/asciidoc/deprecated/messaging.html | 914 ---- .../main/asciidoc/deprecated/migration.html | 594 --- docs/src/main/asciidoc/deprecated/rest.html | 1032 ----- .../main/asciidoc/deprecated/stubrunner.html | 1258 ------ .../asciidoc/deprecated/stubrunner_msg.html | 1247 ------ docs/src/main/asciidoc/index.adoc | 1 - 12 files changed, 10989 deletions(-) delete mode 100644 docs/src/main/asciidoc/deprecated/contract.html delete mode 100644 docs/src/main/asciidoc/deprecated/images/Deps.png delete mode 100644 docs/src/main/asciidoc/deprecated/images/Stubs1.png delete mode 100644 docs/src/main/asciidoc/deprecated/images/Stubs2.png delete mode 100644 docs/src/main/asciidoc/deprecated/index.html delete mode 100644 docs/src/main/asciidoc/deprecated/introduction.html delete mode 100644 docs/src/main/asciidoc/deprecated/messaging.html delete mode 100644 docs/src/main/asciidoc/deprecated/migration.html delete mode 100644 docs/src/main/asciidoc/deprecated/rest.html delete mode 100644 docs/src/main/asciidoc/deprecated/stubrunner.html delete mode 100644 docs/src/main/asciidoc/deprecated/stubrunner_msg.html delete mode 120000 docs/src/main/asciidoc/index.adoc diff --git a/docs/src/main/asciidoc/deprecated/contract.html b/docs/src/main/asciidoc/deprecated/contract.html deleted file mode 100644 index a210b8ade0..0000000000 --- a/docs/src/main/asciidoc/deprecated/contract.html +++ /dev/null @@ -1,1232 +0,0 @@ - - - - - - - -Contract DSL - - - - - - - -
-
-

Contract DSL

-
-
-

Contract DSL in Accurest is written in Groovy, but don’t be alarmed if you didn’t use Groovy before. Knowledge of the language is not really needed as our DSL uses only -a tiny subset of it (namely literals, method calls and closures). What’s more, Accurest’s DSL is designed to be programmer-readable without any knowledge of the DSL itself - - it’s statically typed.

-
-
- - - - - -
- - -Since {messaging_version} you can use the io.codearte.accurest.dsl.Accurest class in your DSL files. -
-
-
-

Let’s look at full example of a contract definition.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'PUT'
-                url '/api/12'
-                headers {
-                        header 'Content-Type': 'application/vnd.com.ofg.twitter-places-analyzer.v1+json'
-                }
-                body '''\
-                [{
-                        "created_at": "Sat Jul 26 09:38:57 +0000 2014",
-                        "id": 492967299297845248,
-                        "id_str": "492967299297845248",
-                        "text": "Gonna see you at Warsaw",
-                        "place":
-                        {
-                                "attributes":{},
-                                "bounding_box":
-                                {
-                                        "coordinates":
-                                                [[
-                                                        [-77.119759,38.791645],
-                                                        [-76.909393,38.791645],
-                                                        [-76.909393,38.995548],
-                                                        [-77.119759,38.995548]
-                                                ]],
-                                        "type":"Polygon"
-                                },
-                                "country":"United States",
-                                "country_code":"US",
-                                "full_name":"Washington, DC",
-                                "id":"01fbe706f872cb32",
-                                "name":"Washington",
-                                "place_type":"city",
-                                "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
-                        }
-                }]
-        '''
-        }
-        response {
-                status 200
-        }
-}
-
-
-
-

Not all features of the DSL are used in example above. If you didn’t find what you are looking for, please check next paragraphs on this page.

-
-
-
-
-

You can easily compile Accurest Contracts to WireMock stubs mapping using standalone maven command: mvn io.codearte.accurest:accurest-maven-plugin:convert.

-
-
-
-
-

Limitations

-
- - - - - -
- - -Accurest doesn’t support XML properly. Please use JSON or help us implement this feature. -
-
-
- - - - - -
- - -Accurest supports equality check on text response. Regular expressions are not yet available. -
-
-
-
-

HTTP Top-Level Elements

-
-

Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        // Definition of HTTP request part of the contract
-        // (this can be a valid request or invalid depending
-        // on type of contract being specified).
-        request {
-                //...
-        }
-
-        // Definition of HTTP response part of the contract
-        // (a service implementing this contract should respond
-        // with following response after receiving request
-        // specified in "request" part above).
-        response {
-                //...
-        }
-
-        // Contract priority, which can be used for overriding
-        // contracts (1 is highest). Priority is optional.
-        priority 1
-}
-
-
-
-
-

Request

-
-

HTTP protocol requires only method and address to be specified in a request. The same information is mandatory in request definition of Accurest contract.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                // HTTP request method (GET/POST/PUT/DELETE).
-                method 'GET'
-
-                // Path component of request URL is specified as follows.
-                urlPath('/users')
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

It is possible to specify whole url instead of just path, but urlPath is the recommended way as it makes the tests host-independent.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'GET'
-
-                // Specifying `url` and `urlPath` in one contract is illegal.
-                url('http://localhost:8888/users')
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

Request may contain query parameters, which are specified in a closure nested in a call to urlPath or url.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                urlPath('/users') {
-
-                        // Each parameter is specified in form
-                        // `'paramName' : paramValue` where parameter value
-                        // may be a simple literal or one of matcher functions,
-                        // all of which are used in this example.
-                        queryParameters {
-
-                                // If a simple literal is used as value
-                                // default matcher function is used (equalTo)
-                                parameter 'limit': 100
-
-                                // `equalTo` function simply compares passed value
-                                // using identity operator (==).
-                                parameter 'filter': equalTo("email")
-
-                                // `containing` function matches strings
-                                // that contains passed substring.
-                                parameter 'gender': value(stub(containing("[mf]")), server('mf'))
-
-                                // `matching` function tests parameter
-                                // against passed regular expression.
-                                parameter 'offset': value(stub(matching("[0-9]+")), server(123))
-
-                                // `notMatching` functions tests if parameter
-                                // does not match passed regular expression.
-                                parameter 'loginStartsWith': value(stub(notMatching(".{0,2}")), server(3))
-                        }
-                }
-
-                //...
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

It may contain additional request headers…​

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // Each header is added in form `'Header-Name' : 'Header-Value'`.
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-
-                //...
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

…​and a request body.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // JSON and XML formats of request body are supported.
-                // Format will be determined from a header or body's content.
-                body '''{ "login" : "john", "name": "John The Contract" }'''
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

Body’s format can also be specified explicitly by invoking one of format functions.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // In this case body will be formatted as XML.
-                body equalToXml(
-                                '''<user><login>john</login><name>John The Contract</name></user>'''
-                )
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-
-

Response

-
-

Minimal response must contain HTTP status code.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-        }
-        response {
-                // Status code sent by the server
-                // in response to request specified above.
-                status 200
-        }
-}
-
-
-
-

Besides status response may contain headers and body, which are specified the same way as in the request (see previous paragraph).

-
-
-
-

Regular expressions

-
-

You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response -should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both -for your test and your server side tests.

-
-
-

Please see the example below:

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method('GET')
-                url $(client(~/\/[0-9]{2}/), server('/12'))
-        }
-        response {
-                status 200
-                body(
-                                id: value(
-                                                client('123'),
-                                                server(regex('[0-9]+'))
-                                ),
-                                surname: $(
-                                                client('Kowalsky'),
-                                                server('Lewandowski')
-                                ),
-                                name: 'Jan',
-                                created: $(client('2014-02-02 12:23:43'), server(execute('currentDate(it)'))),
-                                correlationId: value(client('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'),
-                                                server(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'))
-                                )
-                )
-                headers {
-                        header 'Content-Type': 'text/plain'
-                }
-        }
-}
-
-
-
-
-

Passing optional parameters

-
-

It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:

-
-
-
    -
  • -

    STUB side of the Request

    -
  • -
  • -

    TEST side of the Response

    -
  • -
-
-
-

Example:

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        priority 1
-        request {
-                method 'POST'
-                url '/users/password'
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body(
-                                email: $(stub(optional(regex(email()))), test('abc@abc.com')),
-                                callback_url: $(stub(regex(hostname())), test('http://partners.com'))
-                )
-        }
-        response {
-                status 404
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body(
-                                code: value(stub("123123"), test(optional("123123")))
-                )
-        }
-}
-
-
-
-

By wrapping a part of the body with the optional() method you are in fact creating a regular expression that should be present 0 or more times.

-
-
-

That way for the example above the following test would be generated if you pick Spock:

-
-
-
-
"""
- given:
-  def request = given()
-    .header('Content-Type', 'application/json')
-    .body('''{"email":"abc@abc.com","callback_url":"http://partners.com"}''')
-
- when:
-  def response = given().spec(request)
-    .post("/users/password")
-
- then:
-  response.statusCode == 404
-  response.header('Content-Type')  == 'application/json'
- and:
-  DocumentContext parsedJson = JsonPath.parse(response.body.asString())
-  assertThatJson(parsedJson).field("code").matches("(123123)?")
-"""
-
-
-
-

and the following stub:

-
-
-
-
'''
-{
-  "request" : {
-    "url" : "/users/password",
-    "method" : "POST",
-    "bodyPatterns" : [ {
-      "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]"
-    }, {
-      "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"
-    } ],
-    "headers" : {
-      "Content-Type" : {
-        "equalTo" : "application/json"
-      }
-    }
-  },
-  "response" : {
-    "status" : 404,
-    "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}",
-    "headers" : {
-      "Content-Type" : "application/json"
-    }
-  },
-  "priority" : 1
-}
-'''
-
-
-
-
-

Executing custom methods on server side

-
-

It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" -in the configuration. Please see the examples below:

-
-
-

Groovy DSL

-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'PUT'
-                url $(client(regex('^/api/[0-9]{2}$')), server('/api/12'))
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body '''\
-                                [{
-                                        "text": "Gonna see you at Warsaw"
-                                }]
-                        '''
-        }
-        response {
-                body (
-                                path: $(client('/api/12'), server(regex('^/api/[0-9]{2}$'))),
-                                correlationId: $(client('1223456'), server(execute('isProperCorrelationId($it)')))
-                )
-                status 200
-        }
-}
-
-
-
-
-

Base Mock Spec

-
-
-
abstract class BaseMockMvcSpec extends Specification {
-
-        def setup() {
-                RestAssuredMockMvc.standaloneSetup(new PairIdController())
-        }
-
-        void isProperCorrelationId(Integer correlationId) {
-                assert correlationId == 123456
-        }
-
-        void isEmpty(String value) {
-                assert value == null
-        }
-
-}
-
-
-
-
-
-

JAX-RS support

-
-

Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define protected WebTarget webTarget and server initialization, right now the only option how to test JAX-RS API is to start a web server.

-
-
-

Request with a body needs to have a content type set otherwise application/octet-stream is going to be used.

-
-
-

In order to use JAX-RS mode, use the following settings:

-
-
-
-
testMode == 'JAXRSCLIENT'
-
-
-
-

Example of a test API generated:

-
-
-
-
'''
- // when:
-  Response response = webTarget
-    .path("/users")
-    .queryParam("limit", "10")
-    .queryParam("offset", "20")
-    .queryParam("filter", "email")
-    .queryParam("sort", "name")
-    .queryParam("search", "55")
-    .queryParam("age", "99")
-    .queryParam("name", "Denis.Stepanov")
-    .queryParam("email", "bob@email.com")
-    .request()
-    .method("GET");
-
-  String responseAsString = response.readEntity(String.class);
-
- // then:
-  assertThat(response.getStatus()).isEqualTo(200);
- // and:
-  DocumentContext parsedJson = JsonPath.parse(responseAsString);
-  assertThatJson(parsedJson).field("property1").isEqualTo("a");
-'''
-
-
-
-
-

Messaging Top-Level Elements

-
- - - - - -
- - -Feature available since {messaging_version} -
-
-
-

The DSL for messaging looks a little bit different than the one that focuses on HTTP.

-
-
-

Output triggered by a method

-
-

The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)

-
-
-
-
def dsl = GroovyDsl.make {
-        // Human readable description
-        description 'Some description'
-        // Label by means of which the output message can be triggered
-        label 'some_label'
-        // input to the contract
-        input {
-                // the contract will be triggered by a method
-                triggeredBy('bookReturnedTriggered()')
-        }
-        // output message of the contract
-        outputMessage {
-                // destination to which the output message will be sent
-                sentTo('output')
-                // the body of the output message
-                body('''{ "bookName" : "foo" }''')
-                // the headers of the output message
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

In this case the output message will be sent to output if a method called bookReturnedTriggered will be executed. In the message publisher’s side -we will generate a test that will call that method to trigger the message. On the consumer side you can use the some_label to trigger the message.

-
-
-
-

Output triggered by a message

-
-

The output message can be triggered by receiving a message.

-
-
-
-
def dsl = GroovyDsl.make {
-        description 'Some Description'
-        label 'some_label'
-        // input is a message
-        input {
-                // the message was received from this destination
-                messageFrom('input')
-                // has the following body
-                messageBody([
-                        bookName: 'foo'
-                ])
-                // and the following headers
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('output')
-                body([
-                        bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

In this case the output message will be sent to output if a proper message will be received on the input destination. In the message publisher’s side -we will generate a test that will send the input message to the defined destination. On the consumer side you can either send a message to the input -destination or use the some_label to trigger the message.

-
-
-
-

Consumer / Producer

-
-

In HTTP you have a notion of client/stub and `server/test notation. You can use them also in messaging but we’re providing also the consumer and produer methods -as presented below (note you can use either $ or value methods to provide consumer and producer parts)

-
-
-
-
Accurest.make {
-        label 'some_label'
-        input {
-                messageFrom value(consumer('jms:output'), producer('jms:input'))
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo $(consumer('jms:input'), producer('jms:output'))
-                body([
-                                bookName: 'foo'
-                ])
-        }
-}
-
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/images/Deps.png b/docs/src/main/asciidoc/deprecated/images/Deps.png deleted file mode 100644 index 1426814308101b8ccf6ef95fd195e37c0a180392..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37192 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 A(f|Me diff --git a/docs/src/main/asciidoc/deprecated/images/Stubs1.png b/docs/src/main/asciidoc/deprecated/images/Stubs1.png deleted file mode 100644 index ebadfdb91014284b917367e3d9c9c8a137da3993..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35170 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 diff --git a/docs/src/main/asciidoc/deprecated/index.html b/docs/src/main/asciidoc/deprecated/index.html deleted file mode 100644 index a0ed7a1410..0000000000 --- a/docs/src/main/asciidoc/deprecated/index.html +++ /dev/null @@ -1,3914 +0,0 @@ - - - - - - - -Accurest - - - - - - - -
-
-
-
-

Adam Dudczak, Marcin Grzejszczak, Jakub Kubryński, Karol Lassak, Olga Maciaszek-Sharma, Mariusz Smykuła

-
-
-
-
-

Introduction

-
-
-

Just to make long story short - Accurest is a tool that enables Consumer Driven Contract (CDC) development of JVM-based applications. It is shipped -with Contract Definition Language (DSL). Contract definitions are used by Accurest to produce following resources:

-
-
-
    -
  • -

    JSON stub definitions to be used by Wiremock when doing integration testing on the client code (client tests). -Test code must still be written by hand, test data is produced by Accurest.

    -
  • -
  • -

    Messaging routes if you’re using one. We’re integrating with Spring Integration, Spring Cloud Stream and Apache Camel. You can however set your own integrations if you want to

    -
  • -
  • -

    Acceptance tests (in JUnit or Spock) used to verify if server-side implementation of the API is compliant with the contract (server tests). Full test is generated by Accurest.

    -
  • -
-
-
-

Accurest moves TDD to the level of software architecture.

-
-
-

Why?

-
-

Let us assume that we have a system comprising of multiple microservices:

-
-
-
-Microservices Architecture -
-
-
-

Testing issues

-
-

If we wanted to test the application in top left corner if it can communicate with other services then we could do one of two things:

-
-
-
    -
  • -

    deploy all microservices and perform end to end tests

    -
  • -
  • -

    mock other microservices in unit / integration tests

    -
  • -
-
-
-

Both have their advantages but also a lot of disadvantages. Let’s focus on the latter.

-
-
-

Deploy all microservices and perform end to end tests

-
-
-

Advantages:

-
-
-
    -
  • -

    simulates production

    -
  • -
  • -

    tests real communication between services

    -
  • -
-
-
-

Disadvantages:

-
-
-
    -
  • -

    to test one microservice we would have to deploy 6 microservices, a couple of databases etc.

    -
  • -
  • -

    the environment where the tests would be conducted would be locked for a single suite of tests (i.e. nobody else would be able to run the tests in the meantime).

    -
  • -
  • -

    long to run

    -
  • -
  • -

    very late feedback

    -
  • -
  • -

    extremely hard to debug

    -
  • -
-
-
-

Mock other microservices in unit / integration tests

-
-
-

Advantages:

-
-
-
    -
  • -

    very fast feedback

    -
  • -
  • -

    no infrastructure requirements

    -
  • -
-
-
-

Disadvantages:

-
-
-
    -
  • -

    the implementor of the service creates stubs thus they might have nothing to do with the reality

    -
  • -
  • -

    you can go to production with passing tests and failing production

    -
  • -
-
-
-

To solve the aforementioned issues Accurest with Stub Runner were created. Their main idea is to give you very fast feedback, without the need -to set up the whole world of microservices.

-
-
-
-Stubbed Services -
-
-
-

If you work on stubs then the only applications you need are those that your application is using directly.

-
-
-
-Stubbed Services -
-
-
-

Accurest gives you the certainty that the stubs that you’re using were created by the service that you’re calling. Also if you can use them it means that they were -tested against the producer’s side. In other words - you can trust those stubs.

-
-
-
-
-

Purposes

-
-

The main purposes of Accurest with Stub Runner are:

-
-
-
    -
  • -

    to ensure that WireMock / Messaging stubs (used when developing the client) are doing exactly what actual server-side implementation will do,

    -
  • -
  • -

    to promote ATDD method and Microservices architectural style,

    -
  • -
  • -

    to provide a way to publish changes in contracts that are immediately visible on both sides,

    -
  • -
  • -

    to generate boilerplate test code used on the server side.

    -
  • -
-
-
-
-

Client Side

-
-

During the tests you want to have a Wiremock instance / Messaging route up and running that simulates the service Y. -You would like to feed that instance with a proper stub definition. That stub definition would need -to be valid and should also be reusable on the server side.

-
-
-

Summing it up: On this side, in the stub definition, you can use patterns for request stubbing and you need exact -values for responses.

-
-
-
-

Server Side

-
-

Being a service Y since you are developing your stub, you need to be sure that it’s actually resembling your -concrete implementation. You can’t have a situation where your stub acts in one way and your application on -production behaves in a different way.

-
-
-

That’s why from the provided stub acceptance tests will be generated that will ensure -that your application behaves in the same way as you define in your stub.

-
-
-

Summing it up: On this side, in the stub definition, you need exact values as request and can use patterns/methods -for response verification.

-
-
-
-

Dependencies

-
-

Accurest and Stub Runner are using the following libraries

-
- -
-
- -
-

Below you can find some resources related to Accurest and Stub Runner. Note that some can be outdated since the Accurest project -is under constant development.

-
-
-

Videos

-
-

Olga Maciaszek-Sharma talking about Accurest

-
-
-
- -
-
-
-

Marcin Grzejszczak and Jakub Kubryński talking about Accurest

-
-
-
- -
-
-
- -
-
-

Samples

-
-

Here you can find some working samples. Check the readme of each project for more information.

-
-
-
-
-
-

Contract DSL

-
-
-

Contract DSL in Accurest is written in Groovy, but don’t be alarmed if you didn’t use Groovy before. Knowledge of the language is not really needed as our DSL uses only -a tiny subset of it (namely literals, method calls and closures). What’s more, Accurest’s DSL is designed to be programmer-readable without any knowledge of the DSL itself - - it’s statically typed.

-
-
- - - - - -
- - -Since 1.1.0 you can use the io.codearte.accurest.dsl.Accurest class in your DSL files. -
-
-
-

Let’s look at full example of a contract definition.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'PUT'
-                url '/api/12'
-                headers {
-                        header 'Content-Type': 'application/vnd.com.ofg.twitter-places-analyzer.v1+json'
-                }
-                body '''\
-                [{
-                        "created_at": "Sat Jul 26 09:38:57 +0000 2014",
-                        "id": 492967299297845248,
-                        "id_str": "492967299297845248",
-                        "text": "Gonna see you at Warsaw",
-                        "place":
-                        {
-                                "attributes":{},
-                                "bounding_box":
-                                {
-                                        "coordinates":
-                                                [[
-                                                        [-77.119759,38.791645],
-                                                        [-76.909393,38.791645],
-                                                        [-76.909393,38.995548],
-                                                        [-77.119759,38.995548]
-                                                ]],
-                                        "type":"Polygon"
-                                },
-                                "country":"United States",
-                                "country_code":"US",
-                                "full_name":"Washington, DC",
-                                "id":"01fbe706f872cb32",
-                                "name":"Washington",
-                                "place_type":"city",
-                                "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
-                        }
-                }]
-        '''
-        }
-        response {
-                status 200
-        }
-}
-
-
-
-

Not all features of the DSL are used in example above. If you didn’t find what you are looking for, please check next paragraphs on this page.

-
-
-
-
-

You can easily compile Accurest Contracts to WireMock stubs mapping using standalone maven command: mvn io.codearte.accurest:accurest-maven-plugin:convert.

-
-
-
-
-

Limitations

-
- - - - - -
- - -Accurest doesn’t support XML properly. Please use JSON or help us implement this feature. -
-
-
- - - - - -
- - -Accurest supports equality check on text response. Regular expressions are not yet available. -
-
-
-
-

HTTP Top-Level Elements

-
-

Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        // Definition of HTTP request part of the contract
-        // (this can be a valid request or invalid depending
-        // on type of contract being specified).
-        request {
-                //...
-        }
-
-        // Definition of HTTP response part of the contract
-        // (a service implementing this contract should respond
-        // with following response after receiving request
-        // specified in "request" part above).
-        response {
-                //...
-        }
-
-        // Contract priority, which can be used for overriding
-        // contracts (1 is highest). Priority is optional.
-        priority 1
-}
-
-
-
-
-

Request

-
-

HTTP protocol requires only method and address to be specified in a request. The same information is mandatory in request definition of Accurest contract.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                // HTTP request method (GET/POST/PUT/DELETE).
-                method 'GET'
-
-                // Path component of request URL is specified as follows.
-                urlPath('/users')
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

It is possible to specify whole url instead of just path, but urlPath is the recommended way as it makes the tests host-independent.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'GET'
-
-                // Specifying `url` and `urlPath` in one contract is illegal.
-                url('http://localhost:8888/users')
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

Request may contain query parameters, which are specified in a closure nested in a call to urlPath or url.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                urlPath('/users') {
-
-                        // Each parameter is specified in form
-                        // `'paramName' : paramValue` where parameter value
-                        // may be a simple literal or one of matcher functions,
-                        // all of which are used in this example.
-                        queryParameters {
-
-                                // If a simple literal is used as value
-                                // default matcher function is used (equalTo)
-                                parameter 'limit': 100
-
-                                // `equalTo` function simply compares passed value
-                                // using identity operator (==).
-                                parameter 'filter': equalTo("email")
-
-                                // `containing` function matches strings
-                                // that contains passed substring.
-                                parameter 'gender': value(stub(containing("[mf]")), server('mf'))
-
-                                // `matching` function tests parameter
-                                // against passed regular expression.
-                                parameter 'offset': value(stub(matching("[0-9]+")), server(123))
-
-                                // `notMatching` functions tests if parameter
-                                // does not match passed regular expression.
-                                parameter 'loginStartsWith': value(stub(notMatching(".{0,2}")), server(3))
-                        }
-                }
-
-                //...
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

It may contain additional request headers…​

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // Each header is added in form `'Header-Name' : 'Header-Value'`.
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-
-                //...
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

…​and a request body.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // JSON and XML formats of request body are supported.
-                // Format will be determined from a header or body's content.
-                body '''{ "login" : "john", "name": "John The Contract" }'''
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-

Body’s format can also be specified explicitly by invoking one of format functions.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-
-                // In this case body will be formatted as XML.
-                body equalToXml(
-                                '''<user><login>john</login><name>John The Contract</name></user>'''
-                )
-        }
-
-        response {
-                //...
-        }
-}
-
-
-
-
-

Response

-
-

Minimal response must contain HTTP status code.

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                //...
-        }
-        response {
-                // Status code sent by the server
-                // in response to request specified above.
-                status 200
-        }
-}
-
-
-
-

Besides status response may contain headers and body, which are specified the same way as in the request (see previous paragraph).

-
-
-
-

Regular expressions

-
-

You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response -should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both -for your test and your server side tests.

-
-
-

Please see the example below:

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method('GET')
-                url $(client(~/\/[0-9]{2}/), server('/12'))
-        }
-        response {
-                status 200
-                body(
-                                id: value(
-                                                client('123'),
-                                                server(regex('[0-9]+'))
-                                ),
-                                surname: $(
-                                                client('Kowalsky'),
-                                                server('Lewandowski')
-                                ),
-                                name: 'Jan',
-                                created: $(client('2014-02-02 12:23:43'), server(execute('currentDate(it)'))),
-                                correlationId: value(client('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'),
-                                                server(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'))
-                                )
-                )
-                headers {
-                        header 'Content-Type': 'text/plain'
-                }
-        }
-}
-
-
-
-
-

Passing optional parameters

-
-

It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:

-
-
-
    -
  • -

    STUB side of the Request

    -
  • -
  • -

    TEST side of the Response

    -
  • -
-
-
-

Example:

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        priority 1
-        request {
-                method 'POST'
-                url '/users/password'
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body(
-                                email: $(stub(optional(regex(email()))), test('abc@abc.com')),
-                                callback_url: $(stub(regex(hostname())), test('http://partners.com'))
-                )
-        }
-        response {
-                status 404
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body(
-                                code: value(stub("123123"), test(optional("123123")))
-                )
-        }
-}
-
-
-
-

By wrapping a part of the body with the optional() method you are in fact creating a regular expression that should be present 0 or more times.

-
-
-

That way for the example above the following test would be generated if you pick Spock:

-
-
-
-
"""
- given:
-  def request = given()
-    .header('Content-Type', 'application/json')
-    .body('''{"email":"abc@abc.com","callback_url":"http://partners.com"}''')
-
- when:
-  def response = given().spec(request)
-    .post("/users/password")
-
- then:
-  response.statusCode == 404
-  response.header('Content-Type')  == 'application/json'
- and:
-  DocumentContext parsedJson = JsonPath.parse(response.body.asString())
-  assertThatJson(parsedJson).field("code").matches("(123123)?")
-"""
-
-
-
-

and the following stub:

-
-
-
-
'''
-{
-  "request" : {
-    "url" : "/users/password",
-    "method" : "POST",
-    "bodyPatterns" : [ {
-      "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]"
-    }, {
-      "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"
-    } ],
-    "headers" : {
-      "Content-Type" : {
-        "equalTo" : "application/json"
-      }
-    }
-  },
-  "response" : {
-    "status" : 404,
-    "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}",
-    "headers" : {
-      "Content-Type" : "application/json"
-    }
-  },
-  "priority" : 1
-}
-'''
-
-
-
-
-

Executing custom methods on server side

-
-

It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" -in the configuration. Please see the examples below:

-
-
-

Groovy DSL

-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        request {
-                method 'PUT'
-                url $(client(regex('^/api/[0-9]{2}$')), server('/api/12'))
-                headers {
-                        header 'Content-Type': 'application/json'
-                }
-                body '''\
-                                [{
-                                        "text": "Gonna see you at Warsaw"
-                                }]
-                        '''
-        }
-        response {
-                body (
-                                path: $(client('/api/12'), server(regex('^/api/[0-9]{2}$'))),
-                                correlationId: $(client('1223456'), server(execute('isProperCorrelationId($it)')))
-                )
-                status 200
-        }
-}
-
-
-
-
-

Base Mock Spec

-
-
-
abstract class BaseMockMvcSpec extends Specification {
-
-        def setup() {
-                RestAssuredMockMvc.standaloneSetup(new PairIdController())
-        }
-
-        void isProperCorrelationId(Integer correlationId) {
-                assert correlationId == 123456
-        }
-
-        void isEmpty(String value) {
-                assert value == null
-        }
-
-}
-
-
-
-
-
-

JAX-RS support

-
-

Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define protected WebTarget webTarget and server initialization, right now the only option how to test JAX-RS API is to start a web server.

-
-
-

Request with a body needs to have a content type set otherwise application/octet-stream is going to be used.

-
-
-

In order to use JAX-RS mode, use the following settings:

-
-
-
-
testMode == 'JAXRSCLIENT'
-
-
-
-

Example of a test API generated:

-
-
-
-
'''
- // when:
-  Response response = webTarget
-    .path("/users")
-    .queryParam("limit", "10")
-    .queryParam("offset", "20")
-    .queryParam("filter", "email")
-    .queryParam("sort", "name")
-    .queryParam("search", "55")
-    .queryParam("age", "99")
-    .queryParam("name", "Denis.Stepanov")
-    .queryParam("email", "bob@email.com")
-    .request()
-    .method("GET");
-
-  String responseAsString = response.readEntity(String.class);
-
- // then:
-  assertThat(response.getStatus()).isEqualTo(200);
- // and:
-  DocumentContext parsedJson = JsonPath.parse(responseAsString);
-  assertThatJson(parsedJson).field("property1").isEqualTo("a");
-'''
-
-
-
-
-

Messaging Top-Level Elements

-
- - - - - -
- - -Feature available since 1.1.0 -
-
-
-

The DSL for messaging looks a little bit different than the one that focuses on HTTP.

-
-
-

Output triggered by a method

-
-

The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)

-
-
-
-
def dsl = GroovyDsl.make {
-        // Human readable description
-        description 'Some description'
-        // Label by means of which the output message can be triggered
-        label 'some_label'
-        // input to the contract
-        input {
-                // the contract will be triggered by a method
-                triggeredBy('bookReturnedTriggered()')
-        }
-        // output message of the contract
-        outputMessage {
-                // destination to which the output message will be sent
-                sentTo('output')
-                // the body of the output message
-                body('''{ "bookName" : "foo" }''')
-                // the headers of the output message
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

In this case the output message will be sent to output if a method called bookReturnedTriggered will be executed. In the message publisher’s side -we will generate a test that will call that method to trigger the message. On the consumer side you can use the some_label to trigger the message.

-
-
-
-

Output triggered by a message

-
-

The output message can be triggered by receiving a message.

-
-
-
-
def dsl = GroovyDsl.make {
-        description 'Some Description'
-        label 'some_label'
-        // input is a message
-        input {
-                // the message was received from this destination
-                messageFrom('input')
-                // has the following body
-                messageBody([
-                        bookName: 'foo'
-                ])
-                // and the following headers
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('output')
-                body([
-                        bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

In this case the output message will be sent to output if a proper message will be received on the input destination. In the message publisher’s side -we will generate a test that will send the input message to the defined destination. On the consumer side you can either send a message to the input -destination or use the some_label to trigger the message.

-
-
-
-

Consumer / Producer

-
-

In HTTP you have a notion of client/stub and `server/test notation. You can use them also in messaging but we’re providing also the consumer and produer methods -as presented below (note you can use either $ or value methods to provide consumer and producer parts)

-
-
-
-
Accurest.make {
-        label 'some_label'
-        input {
-                messageFrom value(consumer('jms:output'), producer('jms:input'))
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo $(consumer('jms:input'), producer('jms:output'))
-                body([
-                                bookName: 'foo'
-                ])
-        }
-}
-
-
-
-
-
-
-
-

Accurest HTTP

-
-
-

Gradle Project

-
-

Prerequisites

-
-

In order to use Accurest with Wiremock you have to use gradle or maven plugin.

-
-
-
Add gradle plugin
-
-
-
buildscript {
-        repositories {
-                mavenCentral()
-        }
-        dependencies {
-                classpath 'io.codearte.accurest:accurest-gradle-plugin:${accurest_version}'
-        }
-}
-
-apply plugin: 'groovy'
-apply plugin: 'accurest'
-
-dependencies {
-        testCompile 'org.codehaus.groovy:groovy-all:2.4.6'
-        testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
-        testCompile 'com.jayway.restassured:spring-mock-mvc:2.9.0' // needed if you're going to use Spring MockMvc
-}
-
-
-
-
-
Add maven plugin
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

Read more: accurest-maven-plugin

-
-
-
-
Add stubs
-
-

By default Accurest is looking for stubs in src/test/resources/accurest directory.

-
-
-

Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test. -We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name. -So with following structure

-
-
-
-
src/test/resources/accurest/myservice/shouldCreateUser.groovy
-src/test/resources/accurest/myservice/shouldReturnUser.groovy
-
-
-
-

Accurest will create test class defaultBasePackage.MyService with two methods

-
-
-
    -
  • -

    shouldCreateUser()

    -
  • -
  • -

    shouldReturnUser()

    -
  • -
-
-
-
-
-

Run plugin

-
-

Plugin registers itself to be invoked before check task. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateAccurest task.

-
-
-
-

Configure plugin

-
-

To change default configuration just add accurest snippet to your Gradle config

-
-
-
-
accurest {
-        testMode = 'MockMvc'
-        baseClassForTests = 'org.mycompany.tests'
-        generatedTestSourcesDir = project.file('src/accurest')
-}
-
-
-
-
Configuration options
-
-
    -
  • -

    testMode - defines mode for acceptance tests. By default MockMvc which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to Explicit for real HTTP calls.

    -
  • -
  • -

    imports - array with imports that should be included in generated tests (for example ['org.myorg.Matchers']). By default empty array []

    -
  • -
  • -

    staticImports - array with static imports that should be included in generated tests(for example ['org.myorg.Matchers.*']). By default empty array []

    -
  • -
  • -

    basePackageForTests - specifies base package for all generated tests. By default set to io.codearte.accurest.tests

    -
  • -
  • -

    baseClassForTests - base class for generated tests. By default spock.lang.Specification if using Spock tests.

    -
  • -
  • -

    ruleClassForTests - specifies Rule which should be added to generated test classes.

    -
  • -
  • -

    ignoredFiles - Ant matcher allowing defining stub files for which processing should be skipped. By default empty array []

    -
  • -
  • -

    contractsDslDir - directory containing contracts written using the GroovyDSL. By default $rootDir/src/test/resources/accurest

    -
  • -
  • -

    generatedTestSourcesDir - test source directory where tests generated from Groovy DSL should be placed. By default $buildDir/generated-test-sources/accurest

    -
  • -
  • -

    stubsOutputDir - dir where the generated Wiremock stubs from Groovy DSL should be placed

    -
  • -
  • -

    targetFramework - the target test framework to be used; currently Spock and JUnit are supported with JUnit being the default framework

    -
  • -
-
-
-
-
Base class for tests
-
-

When using Accurest in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.

-
-
-
-
abstract class BaseMockMvcSpec extends Specification {
-
-        def setup() {
-                RestAssuredMockMvc.standaloneSetup(new PairIdController())
-        }
-
-        void isProperCorrelationId(Integer correlationId) {
-                assert correlationId == 123456
-        }
-
-        void isEmpty(String value) {
-                assert value == null
-        }
-
-}
-
-
-
-

In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class -should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.

-
-
-
-
-

Invoking generated tests

-
-

To ensure that provider side is complaint with defined contracts, you need to invoke:

-
-
-
-
./gradlew generateAccurest test
-
-
-
-
-

Accurest on consumer side

-
-

In consumer service you need to configure Accurest plugin in exactly the same way as in case of provider. If you don’t want to use Stub Runner then you need to copy contracts stored in -src/test/resources/accurest and generate WireMock json stubs using:

-
-
-
-
./gradlew generateWireMockClientStubs
-
-
-
-

Note that stubsOutputDir option has to be set for stub generation to work.

-
-
-

When present, json stubs can be used in consumer automated tests.

-
-
-
-
@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
-class LoanApplicationServiceSpec extends Specification {
-
- @ClassRule
- @Shared
- WireMockClassRule wireMockRule == new WireMockClassRule()
-
- @Autowired
- LoanApplicationService sut
-
- def 'should successfully apply for loan'() {
-   given:
-         LoanApplication application =
-                        new LoanApplication(client: new Client(pesel: '12345678901'), amount: 123.123)
-   when:
-        LoanApplicationResult loanApplication == sut.loanApplication(application)
-   then:
-        loanApplication.loanApplicationStatus === LoanApplicationStatus.LOAN_APPLIED
-        loanApplication.rejectionReason === null
- }
-}
-
-
-
-

Underneath LoanApplication makes a call to FraudDetection service. This request is handled by Wiremock server configured using stubs generated by Accurest.

-
-
-
-
-

Using in your Maven project

-
-

Add maven plugin

-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

Read more: accurest-maven-plugin

-
-
-
-

Add stubs

-
-

By default Accurest is looking for stubs in src/test/resources/accurest directory. -Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test. -We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name. -So with following structure

-
-
-
-
src/test/resources/accurest/myservice/shouldCreateUser.groovy
-src/test/resources/accurest/myservice/shouldReturnUser.groovy
-
-
-
-

Accurest will create test class defaultBasePackage.MyService with two methods - - shouldCreateUser() - - shouldReturnUser()

-
-
-
-

Run plugin

-
-

Plugin goal generateTests is assigned to be invoked in phase generate-test-sources. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateTests goal.

-
-
-
-

Configure plugin

-
-

To change default configuration just add configuration section to plugin definition or execution definition.

-
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-    <configuration>
-        <basePackageForTests>com.ofg.twitter.place</basePackageForTests>
-        <baseClassForTests>com.ofg.twitter.place.BaseMockMvcSpec</baseClassForTests>
-    </configuration>
-</plugin>
-
-
-
-
Important configuration options
-
-
    -
  • -

    testMode - defines mode for acceptance tests. By default MockMvc which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to Explicit for real HTTP calls.

    -
  • -
  • -

    basePackageForTests - specifies base package for all generated tests. By default set to io.codearte.accurest.tests.

    -
  • -
  • -

    ruleClassForTests - specifies Rule which should be added to generated test classes.

    -
  • -
  • -

    baseClassForTests - base class for generated tests. By default spock.lang.Specification if using Spock tests.

    -
  • -
  • -

    contractsDir - directory containing contracts written using the GroovyDSL. By default /src/test/resources/accurest.

    -
  • -
  • -

    testFramework - the target test framework to be used; currently Spock and JUnit are supported with Spock being the default framework

    -
  • -
-
-
-

For complete information take a look at Plugin Documentation

-
-
-
-
Base class for tests
-
-
-
When using Accurest in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.
-
-
-
-
-
package org.mycompany.tests
-
-import org.mycompany.ExampleSpringController
-import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
-import spock.lang.Specification
-
-class  MvcSpec extends Specification {
-  def setup() {
-   RestAssuredMockMvc.standaloneSetup(new ExampleSpringController())
-  }
-}
-
-
-
-

In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.

-
-
-
-
-

Invoking generated tests

-
-

Accurest Maven Plugins generates verification code into directory /generated-test-sources/accurest and attach this directory to testCompile goal.

-
-
-

For Groovy Spock code use:

-
-
-
-
<plugin>
-        <groupId>org.codehaus.gmavenplus</groupId>
-        <artifactId>gmavenplus-plugin</artifactId>
-        <version>1.5</version>
-        <executions>
-                <execution>
-                        <goals>
-                                <goal>testCompile</goal>
-                        </goals>
-                </execution>
-        </executions>
-        <configuration>
-                <testSources>
-                        <testSource>
-                                <directory>${project.basedir}/src/test/groovy</directory>
-                                <includes>
-                                        <include>**/*.groovy</include>
-                                </includes>
-                        </testSource>
-                        <testSource>
-                                <directory>${project.build.directory}/generated-test-sources/accurest</directory>
-                                <includes>
-                                        <include>**/*.groovy</include>
-                                </includes>
-                        </testSource>
-                </testSources>
-        </configuration>
-</plugin>
-
-
-
-

To ensure that provider side is complaint with defined contracts, you need to invoke mvn generateTest test

-
-
-
-

Accurest on consumer side

-
-

In consumer service you need to configure Accurest plugin in exactly the same way as in case of provider. You need to copy contracts stored in src/test/resources/accurest and generate Wiremock json stubs using: mvn generateStubs command. By default generated WireMock mapping is stored in directory target/mappings. Your project should create from this generated mappings additional artifact with classifier stubs for easy deploy to maven repository.

-
-
-

Sample configuration:

-
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <version>${accurest-plugin.version}</version>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

When present, json stubs can be used in consumer automated tests.

-
-
-
-
@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
-class LoanApplicationServiceSpec extends Specification {
-
- @ClassRule
- @Shared
- WireMockClassRule wireMockRule == new WireMockClassRule()
-
- @Autowired
- LoanApplicationService sut
-
- def 'should successfully apply for loan'() {
-   given:
-         LoanApplication application =
-                        new LoanApplication(client: new Client(pesel: '12345678901'), amount: 123.123)
-   when:
-        LoanApplicationResult loanApplication == sut.loanApplication(application)
-   then:
-        loanApplication.loanApplicationStatus === LoanApplicationStatus.LOAN_APPLIED
-        loanApplication.rejectionReason === null
- }
-}
-
-
-
-

Underneath LoanApplication makes a call to FraudDetection service. This request is handled by Wiremock server configured using stubs generated by Accurest.

-
-
-
-
-

Scenarios

-
-

It’s possible to handle scenarios with Accurest. All you need to do is to stick to proper naming convention while creating your contracts. The convention requires to include order number followed by the underscore.

-
-
-
-
my_contracts_dir\
-  scenario1\
-    1_login.groovy
-    2_showCart.groovy
-    3_logout.groovy
-
-
-
-

Such tree will cause Accurest generating Wiremock’s scenario with name scenario1 and three steps: - - login marked as Started pointing to: - - showCart marked as Step1 pointing to: - - logout marked as Step2 which will close the scenario. -More details about Wiremock scenarios can be found under [http://wiremock.org/stateful-behaviour.html](http://wiremock.org/stateful-behaviour.html)

-
-
-

Accurest will also generate tests with guaranteed order of execution.

-
-
-
-
-
-

Accurest Messaging

-
-
- - - - - -
- - -Feature available since 1.1.0 -
-
-
-

Accurest allows you to verify your application that uses messaging as means of communication. -All of our integrations are working with Spring but you can also set one yourself.

-
-
-

Integrations

-
-

You can use one of the three integration configurations:

-
-
-
    -
  • -

    Apache Camel

    -
  • -
  • -

    Spring Integration

    -
  • -
  • -

    Spring Cloud Stream

    -
  • -
-
-
-

If you’re using Spring Boot, the aforementioned test configurations will be appended automatically.

-
-
-

You have to provide as a dependency one of the Accurest Messaging modules. Example for Gradle:

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

Manual Integration

-
-

The accurest-messaging-core module contains 3 main interfaces:

-
-
-
    -
  • -

    AccurestMessage - describes a message received / sent to a channel / queue / topic etc.

    -
  • -
  • -

    AccurestMessageBuilder - describes how to build a message

    -
  • -
  • -

    AccurestMessaging - class that allows you to build, send and receive messages

    -
  • -
  • -

    AccurestFilter - interface to filter out the messages that do not follow the pattern from the DSL

    -
  • -
-
-
-

In the generated test the AccurestMessaging is injected via @Inject annotation thus you can use other injection -frameworks than Spring.

-
-
-

You have to provide as a dependency the accurest-messaging-core module. Example for Gradle:

-
-
-
-
testCompile "io.codearte.accurest:accurest-messaging-core:${accurestVersion}"
-
-
-
-
-

Publisher side test generation

-
-

Having the input or outputMessage sections in your DSL will result in creation of tests on the publisher’s side. By default -JUnit tests will be created, however there is also a possibility to create Spock tests.

-
-
-

There are 3 main scenarios that we should take into consideration:

-
-
-
    -
  • -

    Scenario 1: there is no input message that produces an output one. The output message is triggered by a component -inside the application (e.g. scheduler)

    -
  • -
  • -

    Scenario 2: the input message triggers an output message

    -
  • -
  • -

    Scenario 3: the input message is consumed and there is no output message

    -
  • -
-
-
-

Scenario 1 (no input message)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('activemq:output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
- // when:
-  bookReturnedTriggered();
-
- // then:
-  AccurestMessage response = accurestMessaging.receiveMessage("activemq:output");
-  assertThat(response).isNotNull();
-  assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
- // and:
-  DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.getPayload()));
-  assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
'''
- when:
-  bookReturnedTriggered()
-
- then:
-  def response = accurestMessaging.receiveMessage('activemq:output')
-  assert response != null
-  response.getHeader('BOOK-NAME')  == 'foo'
- and:
-  DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.payload))
-  assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
-
-'''
-
-
-
-
-

Scenario 2 (output triggered by input)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                messageFrom('jms:input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
-// given:
- AccurestMessage inputMessage = accurestMessaging.create(
-  "{\\"bookName\\":\\"foo\\"}"
-, headers()
-  .header("sample", "header"));
-
-// when:
- accurestMessaging.send(inputMessage, "jms:input");
-
-// then:
- AccurestMessage response = accurestMessaging.receiveMessage("jms:output");
- assertThat(response).isNotNull();
- assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
-// and:
- DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.getPayload()));
- assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
"""\
-given:
-   def inputMessage = accurestMessaging.create(
-    '''{"bookName":"foo"}''',
-    ['sample': 'header']
-  )
-
-when:
-   accurestMessaging.send(inputMessage, 'jms:input')
-
-then:
-   def response = accurestMessaging.receiveMessage('jms:output')
-   assert response !- null
-   response.getHeader('BOOK-NAME')  == 'foo'
-and:
-   DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.payload))
-   assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
-"""
-
-
-
-
-

Scenario 3 (no output message)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                messageFrom('jms:delete')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-                assertThat('bookWasDeleted()')
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
-// given:
- AccurestMessage inputMessage = accurestMessaging.create(
-        "{\\"bookName\\":\\"foo\\"}"
-, headers()
-        .header("sample", "header"));
-
-// when:
- accurestMessaging.send(inputMessage, "jms:delete");
-
-// then:
- bookWasDeleted();
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
'''
-given:
-         def inputMessage = accurestMessaging.create(
-                \'\'\'{"bookName":"foo"}\'\'\',
-                ['sample': 'header']
-        )
-
-when:
-         accurestMessaging.send(inputMessage, 'jms:delete')
-
-then:
-         noExceptionThrown()
-         bookWasDeleted()
-'''
-
-
-
-
-
-

Consumer Stub Side generation

-
-

Unlike the HTTP part - in Messaging we need to publish the Groovy DSL inside the JAR with a stub. Then it’s parsed on the consumer side -and proper stubbed routes are created.

-
-
-

For more infromation please consult the Stub Runner Messaging sections.

-
-
-

Gradle Setup

-
-

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 Setup

-
-

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

-
-
-
-
-
-
-

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 1.1.0 -
-
-
-

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 nameDefault valueDescription

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.

-
-
-
-
-
-
-

Stub Runner for Messaging

-
-
- - - - - -
- - -Feature available since 1.1.0 -
-
-
-

Stub Runner has the functionality to run the published stubs in memory. It can integrate with the following frameworks out of the box

-
-
-
    -
  • -

    Spring Integration

    -
  • -
  • -

    Spring Cloud Stream

    -
  • -
  • -

    Apache Camel

    -
  • -
-
-
-

It also provides points of entry to integrate with any other solution on the market.

-
-
-

Stub triggering

-
-

To trigger a message it’s enough to use the StubTigger interface:

-
-
-
-
package io.codearte.accurest.stubrunner
-
-interface StubTrigger {
-
-        /**
-         * Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger(String ivyNotation, String labelName)
-
-        /**
-         * Triggers an event by a given label.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger(String labelName)
-
-        /**
-         * Triggers all possible events.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger()
-
-        /**
-         * Returns a mapping of ivy notation of a dependency to all the labels it has.
-         *
-         * Feature related to messaging.
-         */
-        Map<String, Collection<String>> labels()
-}
-
-
-
-

For convenience the StubFinder interface extends StubTrigger so it’s enough to use only one in your tests.

-
-
-

StubTrigger gives you the following options to trigger a message:

-
-
-

Trigger by label

-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-
Trigger by group and artifact ids
-
-
-
stubFinder.trigger('io.codearte.accurest.stubs:camelService', 'return_book_1')
-
-
-
-
-
Trigger by artifact ids
-
-
-
stubFinder.trigger('camelService', 'return_book_1')
-
-
-
-
-
Trigger all messages
-
-
-
stubFinder.trigger()
-
-
-
-
-
-
-

Stub Runner Messaging Camel

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Apache Camel. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-camel:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -camelService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── camelService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── camelService-0.0.1-SNAPSHOT.pom
-                            │   ├── camelService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('jms:input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to jms:output

-
-
-
-
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
-receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the jms:output destination.

-
-
-
-
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
-
-
-
-

Next we’ll want to listen to the output of the message sent to jms:output

-
-
-
-
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
-receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the jms:output destination.

-
-
-
-
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header'])
-
-
-
-
-
-
-

Stub Runner Messaging Integration

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Spring Integration. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-integration:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -integrationService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── integrationService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── integrationService-0.0.1-SNAPSHOT.pom
-                            │   ├── integrationService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and the following Spring Integration Route:

-
-
-
-
<?xml version="1.0" encoding="UTF-8"?>
-<beans:beans xmlns="http://www.springframework.org/schema/integration"
-                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                         xmlns:beans="http://www.springframework.org/schema/beans"
-                         xsi:schemaLocation="http://www.springframework.org/schema/beans
-                        http://www.springframework.org/schema/beans/spring-beans.xsd
-                        http://www.springframework.org/schema/integration
-                        http://www.springframework.org/schema/integration/spring-integration.xsd">
-
-
-        <!-- REQUIRED FOR TESTING -->
-        <bridge input-channel="output"
-                        output-channel="outputTest"/>
-
-        <channel id="outputTest">
-                <queue/>
-        </channel>
-
-</beans:beans>
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to output

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the output destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')
-
-
-
-

Next we’ll want to listen to the output of the message sent to output

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the input destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
-
-
-
-
-
-
-

Stub Runner Messaging Stream

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Spring Stream. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
- - - - - -
- - -In Stub Runner’s integration with Stream the messageFrom or sentTo Strings are resolved -first as a destination of a channel, and then if there is no such destination it’s resolved as a -channel name. -
-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-stream:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -streamService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── streamService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── streamService-0.0.1-SNAPSHOT.pom
-                            │   ├── streamService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('returnBook')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('bookStorage')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('returnBook')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and the following Spring configuration:

-
-
-
-
stubrunner.stubs.repository.root: classpath:m2repo/repository/
-stubrunner.stubs.ids: io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs
-
-spring:
-  cloud:
-    stream:
-      bindings:
-        output:
-          destination: returnBook
-        input:
-          destination: bookStorage
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTrigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to a channel whose destination is returnBook

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('returnBook')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the bookStorage destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
-
-
-
-

Next we’ll want to listen to the output of the message sent to returnBook

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('returnBook')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the output destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
-
-
-
-
-
-
-
-
-

Migration Guide

-
-
-

Migration to 0.4.7

-
-
    -
  • -

    in 0.4.7 we’ve fixed package name (coderate to codearte) so you’ve to do the same in your projects. This means replacing io.coderate.accurest.dsl.GroovyDsl with io.codearte.accurest.dsl.GroovyDsl

    -
  • -
-
-
-
-

Migration to 1.0.0-RC1

-
-
    -
  • -

    from 1.0.0 we’re distinguish ignored contracts from excluded contracts:

    -
  • -
  • -

    excludedFiles pattern tells Accurest to skip processing those files at all

    -
  • -
  • -

    ignoredFiles pattern tells Accurest to generate contracts and tests, but tests will be marked as @Ignore

    -
  • -
  • -

    from 1.0.0 the basePackageForTests behaviour has changed

    -
  • -
  • -

    prior to the change all DSL files had to be under contractsDslDir/basePackageForTests/subpackage resulting in basePackageForTests.subpackage test package creation

    -
  • -
  • -

    now all DSL files have to be under contractsDslDir/subpackage resulting in basePackageForTests.subpackage test package creation

    -
  • -
  • -

    If you don’t migrate to the new approach you will have your tests under contractsDslDir.contractsDslDir.subpackage

    -
  • -
-
-
-
-

Migration to 1.1.0

-
-
    -
  • -

    from 1.1.0 we’re setting JUnit as a default testing utility. You have to pass the following option to keep Spock -as your first choice:

    -
  • -
-
-
-
-
targetFramework = 'Spock'
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/introduction.html b/docs/src/main/asciidoc/deprecated/introduction.html deleted file mode 100644 index 5f173f30de..0000000000 --- a/docs/src/main/asciidoc/deprecated/introduction.html +++ /dev/null @@ -1,797 +0,0 @@ - - - - - - - -Introduction - - - - - - - -
-
-

Introduction

-
-
-

Just to make long story short - Accurest is a tool that enables Consumer Driven Contract (CDC) development of JVM-based applications. It is shipped -with Contract Definition Language (DSL). Contract definitions are used by Accurest to produce following resources:

-
-
-
    -
  • -

    JSON stub definitions to be used by Wiremock when doing integration testing on the client code (client tests). -Test code must still be written by hand, test data is produced by Accurest.

    -
  • -
  • -

    Messaging routes if you’re using one. We’re integrating with Spring Integration, Spring Cloud Stream and Apache Camel. You can however set your own integrations if you want to

    -
  • -
  • -

    Acceptance tests (in JUnit or Spock) used to verify if server-side implementation of the API is compliant with the contract (server tests). Full test is generated by Accurest.

    -
  • -
-
-
-

Accurest moves TDD to the level of software architecture.

-
-
-

Why?

-
-

Let us assume that we have a system comprising of multiple microservices:

-
-
-
-Microservices Architecture -
-
-
-

Testing issues

-
-

If we wanted to test the application in top left corner if it can communicate with other services then we could do one of two things:

-
-
-
    -
  • -

    deploy all microservices and perform end to end tests

    -
  • -
  • -

    mock other microservices in unit / integration tests

    -
  • -
-
-
-

Both have their advantages but also a lot of disadvantages. Let’s focus on the latter.

-
-
-

Deploy all microservices and perform end to end tests

-
-
-

Advantages:

-
-
-
    -
  • -

    simulates production

    -
  • -
  • -

    tests real communication between services

    -
  • -
-
-
-

Disadvantages:

-
-
-
    -
  • -

    to test one microservice we would have to deploy 6 microservices, a couple of databases etc.

    -
  • -
  • -

    the environment where the tests would be conducted would be locked for a single suite of tests (i.e. nobody else would be able to run the tests in the meantime).

    -
  • -
  • -

    long to run

    -
  • -
  • -

    very late feedback

    -
  • -
  • -

    extremely hard to debug

    -
  • -
-
-
-

Mock other microservices in unit / integration tests

-
-
-

Advantages:

-
-
-
    -
  • -

    very fast feedback

    -
  • -
  • -

    no infrastructure requirements

    -
  • -
-
-
-

Disadvantages:

-
-
-
    -
  • -

    the implementor of the service creates stubs thus they might have nothing to do with the reality

    -
  • -
  • -

    you can go to production with passing tests and failing production

    -
  • -
-
-
-

To solve the aforementioned issues Accurest with Stub Runner were created. Their main idea is to give you very fast feedback, without the need -to set up the whole world of microservices.

-
-
-
-Stubbed Services -
-
-
-

If you work on stubs then the only applications you need are those that your application is using directly.

-
-
-
-Stubbed Services -
-
-
-

Accurest gives you the certainty that the stubs that you’re using were created by the service that you’re calling. Also if you can use them it means that they were -tested against the producer’s side. In other words - you can trust those stubs.

-
-
-
-
-

Purposes

-
-

The main purposes of Accurest with Stub Runner are:

-
-
-
    -
  • -

    to ensure that WireMock / Messaging stubs (used when developing the client) are doing exactly what actual server-side implementation will do,

    -
  • -
  • -

    to promote ATDD method and Microservices architectural style,

    -
  • -
  • -

    to provide a way to publish changes in contracts that are immediately visible on both sides,

    -
  • -
  • -

    to generate boilerplate test code used on the server side.

    -
  • -
-
-
-
-

Client Side

-
-

During the tests you want to have a Wiremock instance / Messaging route up and running that simulates the service Y. -You would like to feed that instance with a proper stub definition. That stub definition would need -to be valid and should also be reusable on the server side.

-
-
-

Summing it up: On this side, in the stub definition, you can use patterns for request stubbing and you need exact -values for responses.

-
-
-
-

Server Side

-
-

Being a service Y since you are developing your stub, you need to be sure that it’s actually resembling your -concrete implementation. You can’t have a situation where your stub acts in one way and your application on -production behaves in a different way.

-
-
-

That’s why from the provided stub acceptance tests will be generated that will ensure -that your application behaves in the same way as you define in your stub.

-
-
-

Summing it up: On this side, in the stub definition, you need exact values as request and can use patterns/methods -for response verification.

-
-
-
-

Dependencies

-
-

Accurest and Stub Runner are using the following libraries

-
- -
-
- -
-

Below you can find some resources related to Accurest and Stub Runner. Note that some can be outdated since the Accurest project -is under constant development.

-
-
-

Videos

-
-

Olga Maciaszek-Sharma talking about Accurest

-
-
-
- -
-
-
-

Marcin Grzejszczak and Jakub Kubryński talking about Accurest

-
-
-
- -
-
-
- -
-
-

Samples

-
-

Here you can find some working samples. Check the readme of each project for more information.

-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/messaging.html b/docs/src/main/asciidoc/deprecated/messaging.html deleted file mode 100644 index 623d82b4b3..0000000000 --- a/docs/src/main/asciidoc/deprecated/messaging.html +++ /dev/null @@ -1,914 +0,0 @@ - - - - - - - -Accurest Messaging - - - - - - - -
-
-

Accurest Messaging

-
-
- - - - - -
- - -Feature available since {messaging_version} -
-
-
-

Accurest allows you to verify your application that uses messaging as means of communication. -All of our integrations are working with Spring but you can also set one yourself.

-
-
-

Integrations

-
-

You can use one of the three integration configurations:

-
-
-
    -
  • -

    Apache Camel

    -
  • -
  • -

    Spring Integration

    -
  • -
  • -

    Spring Cloud Stream

    -
  • -
-
-
-

If you’re using Spring Boot, the aforementioned test configurations will be appended automatically.

-
-
-

You have to provide as a dependency one of the Accurest Messaging modules. Example for Gradle:

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

Manual Integration

-
-

The accurest-messaging-core module contains 3 main interfaces:

-
-
-
    -
  • -

    AccurestMessage - describes a message received / sent to a channel / queue / topic etc.

    -
  • -
  • -

    AccurestMessageBuilder - describes how to build a message

    -
  • -
  • -

    AccurestMessaging - class that allows you to build, send and receive messages

    -
  • -
  • -

    AccurestFilter - interface to filter out the messages that do not follow the pattern from the DSL

    -
  • -
-
-
-

In the generated test the AccurestMessaging is injected via @Inject annotation thus you can use other injection -frameworks than Spring.

-
-
-

You have to provide as a dependency the accurest-messaging-core module. Example for Gradle:

-
-
-
-
testCompile "io.codearte.accurest:accurest-messaging-core:${accurestVersion}"
-
-
-
-
-

Publisher side test generation

-
-

Having the input or outputMessage sections in your DSL will result in creation of tests on the publisher’s side. By default -JUnit tests will be created, however there is also a possibility to create Spock tests.

-
-
-

There are 3 main scenarios that we should take into consideration:

-
-
-
    -
  • -

    Scenario 1: there is no input message that produces an output one. The output message is triggered by a component -inside the application (e.g. scheduler)

    -
  • -
  • -

    Scenario 2: the input message triggers an output message

    -
  • -
  • -

    Scenario 3: the input message is consumed and there is no output message

    -
  • -
-
-
-

Scenario 1 (no input message)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('activemq:output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
- // when:
-  bookReturnedTriggered();
-
- // then:
-  AccurestMessage response = accurestMessaging.receiveMessage("activemq:output");
-  assertThat(response).isNotNull();
-  assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
- // and:
-  DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.getPayload()));
-  assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
'''
- when:
-  bookReturnedTriggered()
-
- then:
-  def response = accurestMessaging.receiveMessage('activemq:output')
-  assert response != null
-  response.getHeader('BOOK-NAME')  == 'foo'
- and:
-  DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.payload))
-  assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
-
-'''
-
-
-
-
-

Scenario 2 (output triggered by input)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                messageFrom('jms:input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
-// given:
- AccurestMessage inputMessage = accurestMessaging.create(
-  "{\\"bookName\\":\\"foo\\"}"
-, headers()
-  .header("sample", "header"));
-
-// when:
- accurestMessaging.send(inputMessage, "jms:input");
-
-// then:
- AccurestMessage response = accurestMessaging.receiveMessage("jms:output");
- assertThat(response).isNotNull();
- assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
-// and:
- DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.getPayload()));
- assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
"""\
-given:
-   def inputMessage = accurestMessaging.create(
-    '''{"bookName":"foo"}''',
-    ['sample': 'header']
-  )
-
-when:
-   accurestMessaging.send(inputMessage, 'jms:input')
-
-then:
-   def response = accurestMessaging.receiveMessage('jms:output')
-   assert response !- null
-   response.getHeader('BOOK-NAME')  == 'foo'
-and:
-   DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.payload))
-   assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
-"""
-
-
-
-
-

Scenario 3 (no output message)

-
-

For the given contract:

-
-
-
-
def contractDsl = GroovyDsl.make {
-        label 'some_label'
-        input {
-                messageFrom('jms:delete')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-                assertThat('bookWasDeleted()')
-        }
-}
-
-
-
-

The following JUnit test will be created:

-
-
-
-
'''
-// given:
- AccurestMessage inputMessage = accurestMessaging.create(
-        "{\\"bookName\\":\\"foo\\"}"
-, headers()
-        .header("sample", "header"));
-
-// when:
- accurestMessaging.send(inputMessage, "jms:delete");
-
-// then:
- bookWasDeleted();
-'''
-
-
-
-

And the following Spock test would be created:

-
-
-
-
'''
-given:
-         def inputMessage = accurestMessaging.create(
-                \'\'\'{"bookName":"foo"}\'\'\',
-                ['sample': 'header']
-        )
-
-when:
-         accurestMessaging.send(inputMessage, 'jms:delete')
-
-then:
-         noExceptionThrown()
-         bookWasDeleted()
-'''
-
-
-
-
-
-

Consumer Stub Side generation

-
-

Unlike the HTTP part - in Messaging we need to publish the Groovy DSL inside the JAR with a stub. Then it’s parsed on the consumer side -and proper stubbed routes are created.

-
-
-

For more infromation please consult the Stub Runner Messaging sections.

-
-
-

Gradle Setup

-
-

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 Setup

-
-

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

-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/migration.html b/docs/src/main/asciidoc/deprecated/migration.html deleted file mode 100644 index 351572a2e3..0000000000 --- a/docs/src/main/asciidoc/deprecated/migration.html +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - -Migration Guide - - - - - - - -
-
-

Migration Guide

-
-
-

Migration to 0.4.7

-
-
    -
  • -

    in 0.4.7 we’ve fixed package name (coderate to codearte) so you’ve to do the same in your projects. This means replacing io.coderate.accurest.dsl.GroovyDsl with io.codearte.accurest.dsl.GroovyDsl

    -
  • -
-
-
-
-

Migration to 1.0.0-RC1

-
-
    -
  • -

    from 1.0.0 we’re distinguish ignored contracts from excluded contracts:

    -
  • -
  • -

    excludedFiles pattern tells Accurest to skip processing those files at all

    -
  • -
  • -

    ignoredFiles pattern tells Accurest to generate contracts and tests, but tests will be marked as @Ignore

    -
  • -
  • -

    from 1.0.0 the basePackageForTests behaviour has changed

    -
  • -
  • -

    prior to the change all DSL files had to be under contractsDslDir/basePackageForTests/subpackage resulting in basePackageForTests.subpackage test package creation

    -
  • -
  • -

    now all DSL files have to be under contractsDslDir/subpackage resulting in basePackageForTests.subpackage test package creation

    -
  • -
  • -

    If you don’t migrate to the new approach you will have your tests under contractsDslDir.contractsDslDir.subpackage

    -
  • -
-
-
-
-

Migration to {messaging_version}

-
-
    -
  • -

    from {messaging_version} we’re setting JUnit as a default testing utility. You have to pass the following option to keep Spock -as your first choice:

    -
  • -
-
-
-
-
targetFramework = 'Spock'
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/rest.html b/docs/src/main/asciidoc/deprecated/rest.html deleted file mode 100644 index bab2a70944..0000000000 --- a/docs/src/main/asciidoc/deprecated/rest.html +++ /dev/null @@ -1,1032 +0,0 @@ - - - - - - - -Accurest HTTP - - - - - - - -
-
-

Accurest HTTP

-
-
-

Gradle Project

-
-

Prerequisites

-
-

In order to use Accurest with Wiremock you have to use gradle or maven plugin.

-
-
-
Add gradle plugin
-
-
-
buildscript {
-        repositories {
-                mavenCentral()
-        }
-        dependencies {
-                classpath 'io.codearte.accurest:accurest-gradle-plugin:${accurest_version}'
-        }
-}
-
-apply plugin: 'groovy'
-apply plugin: 'accurest'
-
-dependencies {
-        testCompile 'org.codehaus.groovy:groovy-all:2.4.6'
-        testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
-        testCompile 'com.jayway.restassured:spring-mock-mvc:2.9.0' // needed if you're going to use Spring MockMvc
-}
-
-
-
-
-
Add maven plugin
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

Read more: accurest-maven-plugin

-
-
-
-
Add stubs
-
-

By default Accurest is looking for stubs in src/test/resources/accurest directory.

-
-
-

Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test. -We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name. -So with following structure

-
-
-
-
src/test/resources/accurest/myservice/shouldCreateUser.groovy
-src/test/resources/accurest/myservice/shouldReturnUser.groovy
-
-
-
-

Accurest will create test class defaultBasePackage.MyService with two methods

-
-
-
    -
  • -

    shouldCreateUser()

    -
  • -
  • -

    shouldReturnUser()

    -
  • -
-
-
-
-
-

Run plugin

-
-

Plugin registers itself to be invoked before check task. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateAccurest task.

-
-
-
-

Configure plugin

-
-

To change default configuration just add accurest snippet to your Gradle config

-
-
-
-
accurest {
-        testMode = 'MockMvc'
-        baseClassForTests = 'org.mycompany.tests'
-        generatedTestSourcesDir = project.file('src/accurest')
-}
-
-
-
-
Configuration options
-
-
    -
  • -

    testMode - defines mode for acceptance tests. By default MockMvc which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to Explicit for real HTTP calls.

    -
  • -
  • -

    imports - array with imports that should be included in generated tests (for example ['org.myorg.Matchers']). By default empty array []

    -
  • -
  • -

    staticImports - array with static imports that should be included in generated tests(for example ['org.myorg.Matchers.*']). By default empty array []

    -
  • -
  • -

    basePackageForTests - specifies base package for all generated tests. By default set to io.codearte.accurest.tests

    -
  • -
  • -

    baseClassForTests - base class for generated tests. By default spock.lang.Specification if using Spock tests.

    -
  • -
  • -

    ruleClassForTests - specifies Rule which should be added to generated test classes.

    -
  • -
  • -

    ignoredFiles - Ant matcher allowing defining stub files for which processing should be skipped. By default empty array []

    -
  • -
  • -

    contractsDslDir - directory containing contracts written using the GroovyDSL. By default $rootDir/src/test/resources/accurest

    -
  • -
  • -

    generatedTestSourcesDir - test source directory where tests generated from Groovy DSL should be placed. By default $buildDir/generated-test-sources/accurest

    -
  • -
  • -

    stubsOutputDir - dir where the generated Wiremock stubs from Groovy DSL should be placed

    -
  • -
  • -

    targetFramework - the target test framework to be used; currently Spock and JUnit are supported with JUnit being the default framework

    -
  • -
-
-
-
-
Base class for tests
-
-

When using Accurest in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.

-
-
-
-
abstract class BaseMockMvcSpec extends Specification {
-
-        def setup() {
-                RestAssuredMockMvc.standaloneSetup(new PairIdController())
-        }
-
-        void isProperCorrelationId(Integer correlationId) {
-                assert correlationId == 123456
-        }
-
-        void isEmpty(String value) {
-                assert value == null
-        }
-
-}
-
-
-
-

In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class -should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.

-
-
-
-
-

Invoking generated tests

-
-

To ensure that provider side is complaint with defined contracts, you need to invoke:

-
-
-
-
./gradlew generateAccurest test
-
-
-
-
-

Accurest on consumer side

-
-

In consumer service you need to configure Accurest plugin in exactly the same way as in case of provider. If you don’t want to use Stub Runner then you need to copy contracts stored in -src/test/resources/accurest and generate WireMock json stubs using:

-
-
-
-
./gradlew generateWireMockClientStubs
-
-
-
-

Note that stubsOutputDir option has to be set for stub generation to work.

-
-
-

When present, json stubs can be used in consumer automated tests.

-
-
-
-
@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
-class LoanApplicationServiceSpec extends Specification {
-
- @ClassRule
- @Shared
- WireMockClassRule wireMockRule == new WireMockClassRule()
-
- @Autowired
- LoanApplicationService sut
-
- def 'should successfully apply for loan'() {
-   given:
-         LoanApplication application =
-                        new LoanApplication(client: new Client(pesel: '12345678901'), amount: 123.123)
-   when:
-        LoanApplicationResult loanApplication == sut.loanApplication(application)
-   then:
-        loanApplication.loanApplicationStatus === LoanApplicationStatus.LOAN_APPLIED
-        loanApplication.rejectionReason === null
- }
-}
-
-
-
-

Underneath LoanApplication makes a call to FraudDetection service. This request is handled by Wiremock server configured using stubs generated by Accurest.

-
-
-
-
-

Using in your Maven project

-
-

Add maven plugin

-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

Read more: accurest-maven-plugin

-
-
-
-

Add stubs

-
-

By default Accurest is looking for stubs in src/test/resources/accurest directory. -Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test. -We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name. -So with following structure

-
-
-
-
src/test/resources/accurest/myservice/shouldCreateUser.groovy
-src/test/resources/accurest/myservice/shouldReturnUser.groovy
-
-
-
-

Accurest will create test class defaultBasePackage.MyService with two methods - - shouldCreateUser() - - shouldReturnUser()

-
-
-
-

Run plugin

-
-

Plugin goal generateTests is assigned to be invoked in phase generate-test-sources. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateTests goal.

-
-
-
-

Configure plugin

-
-

To change default configuration just add configuration section to plugin definition or execution definition.

-
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-                <goal>generateTests</goal>
-            </goals>
-        </execution>
-    </executions>
-    <configuration>
-        <basePackageForTests>com.ofg.twitter.place</basePackageForTests>
-        <baseClassForTests>com.ofg.twitter.place.BaseMockMvcSpec</baseClassForTests>
-    </configuration>
-</plugin>
-
-
-
-
Important configuration options
-
-
    -
  • -

    testMode - defines mode for acceptance tests. By default MockMvc which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to Explicit for real HTTP calls.

    -
  • -
  • -

    basePackageForTests - specifies base package for all generated tests. By default set to io.codearte.accurest.tests.

    -
  • -
  • -

    ruleClassForTests - specifies Rule which should be added to generated test classes.

    -
  • -
  • -

    baseClassForTests - base class for generated tests. By default spock.lang.Specification if using Spock tests.

    -
  • -
  • -

    contractsDir - directory containing contracts written using the GroovyDSL. By default /src/test/resources/accurest.

    -
  • -
  • -

    testFramework - the target test framework to be used; currently Spock and JUnit are supported with Spock being the default framework

    -
  • -
-
-
-

For complete information take a look at Plugin Documentation

-
-
-
-
Base class for tests
-
-
-
When using Accurest in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.
-
-
-
-
-
package org.mycompany.tests
-
-import org.mycompany.ExampleSpringController
-import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
-import spock.lang.Specification
-
-class  MvcSpec extends Specification {
-  def setup() {
-   RestAssuredMockMvc.standaloneSetup(new ExampleSpringController())
-  }
-}
-
-
-
-

In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.

-
-
-
-
-

Invoking generated tests

-
-

Accurest Maven Plugins generates verification code into directory /generated-test-sources/accurest and attach this directory to testCompile goal.

-
-
-

For Groovy Spock code use:

-
-
-
-
<plugin>
-        <groupId>org.codehaus.gmavenplus</groupId>
-        <artifactId>gmavenplus-plugin</artifactId>
-        <version>1.5</version>
-        <executions>
-                <execution>
-                        <goals>
-                                <goal>testCompile</goal>
-                        </goals>
-                </execution>
-        </executions>
-        <configuration>
-                <testSources>
-                        <testSource>
-                                <directory>${project.basedir}/src/test/groovy</directory>
-                                <includes>
-                                        <include>**/*.groovy</include>
-                                </includes>
-                        </testSource>
-                        <testSource>
-                                <directory>${project.build.directory}/generated-test-sources/accurest</directory>
-                                <includes>
-                                        <include>**/*.groovy</include>
-                                </includes>
-                        </testSource>
-                </testSources>
-        </configuration>
-</plugin>
-
-
-
-

To ensure that provider side is complaint with defined contracts, you need to invoke mvn generateTest test

-
-
-
-

Accurest on consumer side

-
-

In consumer service you need to configure Accurest plugin in exactly the same way as in case of provider. You need to copy contracts stored in src/test/resources/accurest and generate Wiremock json stubs using: mvn generateStubs command. By default generated WireMock mapping is stored in directory target/mappings. Your project should create from this generated mappings additional artifact with classifier stubs for easy deploy to maven repository.

-
-
-

Sample configuration:

-
-
-
-
<plugin>
-    <groupId>io.codearte.accurest</groupId>
-    <artifactId>accurest-maven-plugin</artifactId>
-    <version>${accurest-plugin.version}</version>
-    <executions>
-        <execution>
-            <goals>
-                <goal>convert</goal>
-                <goal>generateStubs</goal>
-            </goals>
-        </execution>
-    </executions>
-</plugin>
-
-
-
-

When present, json stubs can be used in consumer automated tests.

-
-
-
-
@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
-class LoanApplicationServiceSpec extends Specification {
-
- @ClassRule
- @Shared
- WireMockClassRule wireMockRule == new WireMockClassRule()
-
- @Autowired
- LoanApplicationService sut
-
- def 'should successfully apply for loan'() {
-   given:
-         LoanApplication application =
-                        new LoanApplication(client: new Client(pesel: '12345678901'), amount: 123.123)
-   when:
-        LoanApplicationResult loanApplication == sut.loanApplication(application)
-   then:
-        loanApplication.loanApplicationStatus === LoanApplicationStatus.LOAN_APPLIED
-        loanApplication.rejectionReason === null
- }
-}
-
-
-
-

Underneath LoanApplication makes a call to FraudDetection service. This request is handled by Wiremock server configured using stubs generated by Accurest.

-
-
-
-
-

Scenarios

-
-

It’s possible to handle scenarios with Accurest. All you need to do is to stick to proper naming convention while creating your contracts. The convention requires to include order number followed by the underscore.

-
-
-
-
my_contracts_dir\
-  scenario1\
-    1_login.groovy
-    2_showCart.groovy
-    3_logout.groovy
-
-
-
-

Such tree will cause Accurest generating Wiremock’s scenario with name scenario1 and three steps: - - login marked as Started pointing to: - - showCart marked as Step1 pointing to: - - logout marked as Step2 which will close the scenario. -More details about Wiremock scenarios can be found under [http://wiremock.org/stateful-behaviour.html](http://wiremock.org/stateful-behaviour.html)

-
-
-

Accurest will also generate tests with guaranteed order of execution.

-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/stubrunner.html b/docs/src/main/asciidoc/deprecated/stubrunner.html deleted file mode 100644 index 901a8fd60f..0000000000 --- a/docs/src/main/asciidoc/deprecated/stubrunner.html +++ /dev/null @@ -1,1258 +0,0 @@ - - - - - - - -Stub Runner - - - - - - - -
-
-

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 nameDefault valueDescription

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.

-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/deprecated/stubrunner_msg.html b/docs/src/main/asciidoc/deprecated/stubrunner_msg.html deleted file mode 100644 index ae7b656eea..0000000000 --- a/docs/src/main/asciidoc/deprecated/stubrunner_msg.html +++ /dev/null @@ -1,1247 +0,0 @@ - - - - - - - -Stub Runner for Messaging - - - - - - - -
-
-

Stub Runner for Messaging

-
-
- - - - - -
- - -Feature available since {messaging_version} -
-
-
-

Stub Runner has the functionality to run the published stubs in memory. It can integrate with the following frameworks out of the box

-
-
-
    -
  • -

    Spring Integration

    -
  • -
  • -

    Spring Cloud Stream

    -
  • -
  • -

    Apache Camel

    -
  • -
-
-
-

It also provides points of entry to integrate with any other solution on the market.

-
-
-

Stub triggering

-
-

To trigger a message it’s enough to use the StubTigger interface:

-
-
-
-
package io.codearte.accurest.stubrunner
-
-interface StubTrigger {
-
-        /**
-         * Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger(String ivyNotation, String labelName)
-
-        /**
-         * Triggers an event by a given label.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger(String labelName)
-
-        /**
-         * Triggers all possible events.
-         *
-         * Feature related to messaging.
-         *
-         * @return true - if managed to run a trigger
-         */
-        boolean trigger()
-
-        /**
-         * Returns a mapping of ivy notation of a dependency to all the labels it has.
-         *
-         * Feature related to messaging.
-         */
-        Map<String, Collection<String>> labels()
-}
-
-
-
-

For convenience the StubFinder interface extends StubTrigger so it’s enough to use only one in your tests.

-
-
-

StubTrigger gives you the following options to trigger a message:

-
-
-

Trigger by label

-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-
Trigger by group and artifact ids
-
-
-
stubFinder.trigger('io.codearte.accurest.stubs:camelService', 'return_book_1')
-
-
-
-
-
Trigger by artifact ids
-
-
-
stubFinder.trigger('camelService', 'return_book_1')
-
-
-
-
-
Trigger all messages
-
-
-
stubFinder.trigger()
-
-
-
-
-
-
-

Stub Runner Messaging Camel

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Apache Camel. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-camel:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -camelService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── camelService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── camelService-0.0.1-SNAPSHOT.pom
-                            │   ├── camelService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('jms:input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('jms:output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to jms:output

-
-
-
-
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
-receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the jms:output destination.

-
-
-
-
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
-
-
-
-

Next we’ll want to listen to the output of the message sent to jms:output

-
-
-
-
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
-receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the jms:output destination.

-
-
-
-
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header'])
-
-
-
-
-
-
-

Stub Runner Messaging Integration

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Spring Integration. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-integration:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -integrationService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── integrationService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── integrationService-0.0.1-SNAPSHOT.pom
-                            │   ├── integrationService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('output')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('input')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('output')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and the following Spring Integration Route:

-
-
-
-
<?xml version="1.0" encoding="UTF-8"?>
-<beans:beans xmlns="http://www.springframework.org/schema/integration"
-                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                         xmlns:beans="http://www.springframework.org/schema/beans"
-                         xsi:schemaLocation="http://www.springframework.org/schema/beans
-                        http://www.springframework.org/schema/beans/spring-beans.xsd
-                        http://www.springframework.org/schema/integration
-                        http://www.springframework.org/schema/integration/spring-integration.xsd">
-
-
-        <!-- REQUIRED FOR TESTING -->
-        <bridge input-channel="output"
-                        output-channel="outputTest"/>
-
-        <channel id="outputTest">
-                <queue/>
-        </channel>
-
-</beans:beans>
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to output

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the output destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')
-
-
-
-

Next we’ll want to listen to the output of the message sent to output

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the input destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
-
-
-
-
-
-
-

Stub Runner Messaging Stream

-
-

Accurest Stub Runner’s messaging module gives you an easy way to integrate with Spring Stream. -For the provided artifacts it will automatically download the stubs and register the required -routes.

-
-
- - - - - -
- - -In Stub Runner’s integration with Stream the messageFrom or sentTo Strings are resolved -first as a destination of a channel, and then if there is no such destination it’s resolved as a -channel name. -
-
-
-

Adding it to the project

-
-

To use it you have to add the following dependency to your project (example for Gradle):

-
-
-
-
testCompile "io.codearte.accurest:stub-runner-messaging-stream:${accurestVersion}"
-
-
-
-
-

Examples

-
-
Stubs structure
-
-

Let us assume that we have the following Maven repository with a deployed stubs for the -streamService application.

-
-
-
-
└── .m2
-    └── repository
-        └── io
-            └── codearte
-                └── accurest
-                    └── stubs
-                        └── streamService
-                            ├── 0.0.1-SNAPSHOT
-                            │   ├── streamService-0.0.1-SNAPSHOT.pom
-                            │   ├── streamService-0.0.1-SNAPSHOT-stubs.jar
-                            │   └── maven-metadata-local.xml
-                            └── maven-metadata-local.xml
-
-
-
-

And the stubs contain the following structure:

-
-
-
-
├── META-INF
-│   └── MANIFEST.MF
-└── repository
-    ├── accurest
-    │   ├── bookDeleted.groovy
-    │   ├── bookReturned1.groovy
-    │   └── bookReturned2.groovy
-    └── mappings
-
-
-
-

Let’s consider the following contracts (let' number it with 1):

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_1'
-        input {
-                triggeredBy('bookReturnedTriggered()')
-        }
-        outputMessage {
-                sentTo('returnBook')
-                body('''{ "bookName" : "foo" }''')
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and number 2

-
-
-
-
io.codearte.accurest.dsl.GroovyDsl.make {
-        label 'return_book_2'
-        input {
-                messageFrom('bookStorage')
-                messageBody([
-                                bookName: 'foo'
-                ])
-                messageHeaders {
-                        header('sample', 'header')
-                }
-        }
-        outputMessage {
-                sentTo('returnBook')
-                body([
-                                bookName: 'foo'
-                ])
-                headers {
-                        header('BOOK-NAME', 'foo')
-                }
-        }
-}
-
-
-
-

and the following Spring configuration:

-
-
-
-
stubrunner.stubs.repository.root: classpath:m2repo/repository/
-stubrunner.stubs.ids: io.codearte.accurest.stubs:streamService:0.0.1-SNAPSHOT:stubs
-
-spring:
-  cloud:
-    stream:
-      bindings:
-        output:
-          destination: returnBook
-        input:
-          destination: bookStorage
-
-
-
-
-
Scenario 1 (no input message)
-
-

So as to trigger a message via the return_book_1 label we’ll use the StubTrigger interface as follows

-
-
-
-
stubFinder.trigger('return_book_1')
-
-
-
-

Next we’ll want to listen to the output of the message sent to a channel whose destination is returnBook

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('returnBook')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 2 (output triggered by input)
-
-

Since the route is set for you it’s enough to just send a message to the bookStorage destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
-
-
-
-

Next we’ll want to listen to the output of the message sent to returnBook

-
-
-
-
AccurestMessage receivedMessage = messaging.receiveMessage('returnBook')
-
-
-
-

And the received message would pass the following assertions

-
-
-
-
receivedMessage != null
-assertJsons(receivedMessage.payload)
-receivedMessage.headers.get('BOOK-NAME') == 'foo'
-
-
-
-
-
Scenario 3 (input with no output)
-
-

Since the route is set for you it’s enough to just send a message to the output destination.

-
-
-
-
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
-
-
-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc deleted file mode 120000 index 28dc0a7e0c..0000000000 --- a/docs/src/main/asciidoc/index.adoc +++ /dev/null @@ -1 +0,0 @@ -spring-cloud-contract-verifier.adoc \ No newline at end of file