diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 5b5d08e9e4..42f57e8f79 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -1209,1221 +1209,4 @@ Some of the properties that are repetitive can be set using system properties or - 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*[:github-tag: master -:github-repo: Codearte/accurest -:github-raw: http://raw.github.com/{github-repo}/{github-tag} -:github-code: http://github.com/{github-repo}/tree/{github-tag} -:toc: left - -Welcome to the AccuREST Wiki! - -Please follow to the Introduction page to start your journey with Consumer Driven Contracts in JVM - -= 1. 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 __REST 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. -* Acceptance tests (in 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? - -The main purposes of AccuREST are: - - - to ensure that WireMock 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. - -= 2. Using in your project - -== Prerequisites - -In order to use Accurest with Wiremock you have to have Wiremock in version at least **2.0.0-beta** . Of course the higher the better :) - -= 2.1. Gradle Project - -== Prerequisites - -In order to use Accurest with Wiremock you have to have Wiremock in version at least **2.0.0-beta** . Of course the higher the better :) - -== Add gradle plugin - -[source,groovy,indent=0] ----- -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'io.codearte.accurest:accurest-gradle-plugin:0.9.9' - } -} - -apply plugin: 'accurest' - -dependencies { - testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' - testCompile 'com.github.tomakehurst:wiremock:2.0.4-beta' // you have to use WireMock with 2.0 versions of JsonPath - testCompile 'com.jayway.restassured:rest-assured:2.4.1' - testCompile 'com.jayway.restassured:spring-mock-mvc:2.4.1' // needed if you're going to use Spring MockMvc -} ----- - -== Add maven plugin - -[source,xml,indent=0] ----- - - io.codearte.accurest - accurest-maven-plugin - - - - generateStubs - generateTests // for JUnit tests, use generateSpecs for Spock Specification - - - - ----- - - -Read more: [accurest-maven-plugin](https://github.com/Codearte/accurest-maven-plugin) - -== Add stubs - -By default Accurest is looking for stubs in src/test/resources/stubs 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/stubs/myservice/shouldCreateUser.groovy -src/test/resources/stubs/myservice/shouldReturnUser.groovy - -Accurest will create test class `defaultBasePackage.MyService` with two methods - - shouldCreateUser() - - shouldReturnUser() - -== Run plugin - -Plugin registers itself to be invoked before `compileTestGroovy` 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 - -[source,groovy,indent=0] ----- -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` - - **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/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 Spock 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. - -[source,groovy,indent=0] ----- -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 - -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. You need to copy contracts stored in src/test/resources/stubs and generate Wiremock json stubs using: `./gradlew generateWireMockClientStubs` command. Note that `stubsOutputDir` option has to be set for stub generation to work. - -When present, json stubs can be used in consumer automated tests. - -[source,groovy,indent=0] ----- -@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. - -= 2.2. Using in your Maven project - -== Add maven plugin - -[source,xml,indent=0] ----- - - io.codearte.accurest - accurest-maven-plugin - - - - convert - generateStubs - generateTests - - - - ----- - -Read more: [accurest-maven-plugin](https://github.com/Codearte/accurest-maven-plugin) - -== Add stubs - -By default Accurest is looking for stubs in `src/test/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 - -[source,groovy,indent=0] ----- -src/test/accurest/myservice/shouldCreateUser.groovy -src/test/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. - -[source,xml,indent=0] ----- - - io.codearte.accurest - accurest-maven-plugin - - - - convert - generateStubs - generateTests - - - - - com.ofg.twitter.place - com.ofg.twitter.place.BaseMockMvcSpec - - ----- - -=== 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`. - - **contractsDir** - directory containing contracts written using the GroovyDSL. By default `/src/test/accurest`. - - **generatedTestSourcesDir** - test source directory where tests generated from Groovy DSL should be placed. By default `target/generated-test-sources/accurest`. - - **mappingsDir** - dir where the generated Wiremock stubs from Groovy DSL should be placed. - - **testFramework** - the target test framework to be used; currently Spock and JUnit are supported with Spock 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. - -[source,groovy,indent=0] ----- -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: - -[source,xml,indent=0] ----- - - org.codehaus.gmavenplus - gmavenplus-plugin - 1.5 - - - - testCompile - - - - - - - ${project.basedir}/src/test/groovy - - **/*.groovy - - - - ${project.build.directory}/generated-test-sources/accurest - - **/*.groovy - - - - - ----- - -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/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: - -[source,xml,indent=0] ----- - - io.codearte.accurest - accurest-maven-plugin - ${accurest.version} - - - - convert - generateStubs - - - - ----- - -When present, json stubs can be used in consumer automated tests. - -[source,groovy,indent=0] ----- -@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. - -= 3. 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. - -Let's look at full example of a contract definition. - -[source,groovy,indent=0] ----- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - method 'POST' - urlPath('/users') { - queryParameters { - parameter 'limit': 100 - parameter 'offset': containing("1") - parameter 'filter': "email" - } - } - headers { - header 'Content-Type': 'application/json' - } - body '''{ "login" : "john", "name": "John The Contract" }''' - } - response { - status 200 - headers { - header 'Location': '/users/john' - } - } -} ----- - -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`. - -== 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. - -[source,groovy,indent=0] ----- -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. - -[source,groovy,indent=0] ----- -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**. - -[source,groovy,indent=0] ----- -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`. - -[source,groovy,indent=0] ----- -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': containing("[mf]") - - // `matching` function tests parameter - // against passed regular expression. - parameter 'offset': matching("[0-9]+") - - // `notMatching` functions tests if parameter - // does not match passed regular expression. - parameter 'loginStartsWith': notMatching(".{0,2}") - } - } - - ... - } - - response { - ... - } -} ----- - -It may contain additional **request headers**... - -[source,groovy,indent=0] ----- -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**. - -[source,groovy,indent=0] ----- -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. - -[source,groovy,indent=0] ----- -io.codearte.accurest.dsl.GroovyDsl.make { - request { - ... - - // In this case body will be formatted as XML. - body equalToXml( - '''johnJohn The Contract''' - ) - } - - response { - ... - } -} ----- - -== Response - -Minimal response must contain **HTTP status code**. - -[source,groovy,indent=0] ----- -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: - -[source,groovy,indent=0] ----- -io.codearte.accurest.dsl.GroovyDsl groovyDsl = 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({ 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: - -[source,groovy,indent=0] ----- -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"))), - message: "User not found by email = [${value(test(regex(email())), stub('not.existing@user.com'))}]" - ) - } -} ----- - -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: - -[source,groovy,indent=0] ----- - 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()) - !parsedJson.read('''$[?(@.code =~ /(123123)?/)]''', JSONArray).empty - !parsedJson.read('''$[?(@.message =~ /User not found by email = \\[[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}\\]/)]''', JSONArray).empty - ----- - -and the following stub: - -[source,javascript,indent=0] ----- -{ - "request" : { - "url" : "/users/password", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?/)]" - }, { - "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4})?/)]" - } ], - "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 - -[source,groovy,indent=0] ----- -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 - -[source,groovy,indent=0] ----- -abstract class BaseMockMvcSpec extends Specification { - - def setup() { - RestAssuredMockMvc.standaloneSetup(new PairIdController()) - } - - void isProperCorrelationId(Integer correlationId) { - assert correlationId == 123456 - } -} ----- - -== 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: - -[source,groovy,indent=0] ----- -testMode = 'JAXRSCLIENT' ----- - -Example of a test API generated: - -[source,groovy,indent=0] ----- -class FraudDetectionServiceSpec extends MvcSpec { - - def shouldMarkClientAsNotFraud() { - when: - def response = webTarget - .path('/fraudcheck') - .request() - .method('put', entity('{"clientPesel":"1234567890","loanAmount":123.123}', 'application/vnd.fraud.v1+json')) - - String responseAsString = response.readEntity(String) - - then: - response.status == 200 - response.getHeaderString('Content-Type') == 'application/vnd.fraud.v1+json' - and: - def responseBody = new JsonSlurper().parseText(responseAsString) - responseBody.fraudCheckStatus == "OK" - assertThatRejectionReasonIsNull(responseBody.rejectionReason) - } - - def shouldMarkClientAsFraud() { - when: - def response = webTarget - .path('/fraudcheck') - .request() - .method('put', entity('{"clientPesel":"1234567890","loanAmount":99999}', 'application/vnd.fraud.v1+json')) - - String responseAsString = response.readEntity(String) - - then: - response.status == 200 - response.getHeaderString('Content-Type') == 'application/vnd.fraud.v1+json' - and: - def responseBody = new JsonSlurper().parseText(responseAsString) - responseBody.fraudCheckStatus ==~ java.util.regex.Pattern.compile('[A-Z]{5}') - responseBody.rejectionReason == "Amount too high" - } - -} ----- - -= 4. Client Side - -During the tests you want to have a Wiremock instance 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 from the Wiremock's perspective but 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. - -= 5. 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. - -= 6. Examples - -[source,groovy,indent=0] ----- -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 - } -} ----- - -= 7. 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. - -[source,indent=0] ----- -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. - -= 8. 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). Copying the JSON files manually is out of the question. - -In this article you'll see how to prepare your project to start publishing stubs as JARs and how to use Stub Runner in your tests to run WireMock servers and feed them with stub definitions. - -== 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: - -[source,groovy,indent=0] ----- - apply plugin: 'maven-publish' - - ext { - wiremockStubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") - wiremockStubsOutputDir = new File(wiremockStubsOutputDirRoot) - } - - accurest { - targetFramework = 'Spock' - testMode = 'MockMvc' - baseClassForTests = 'com.toomuchcoding.MvcSpec' - contractsDslDir = file("${project.projectDir.absolutePath}/mappings/") - generatedTestSourcesDir = file("${project.buildDir}/generated-sources/") - stubsOutputDir = wiremockStubsOutputDir - } - - task stubsJar(type: Jar, dependsOn: ["generateWireMockClientStubs"]) { - baseName = "${project.name}-stubs" - from wiremockStubsOutputDirRoot - } - - 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](https://github.com/Codearte/accurest-maven-plugin/=publishing-wiremock-stubs-projectf-stubsjar) - -== Using Stub Runner to automate running stubs - -Stub Runner automates downloading stubs from a Maven repository (that includes also the local Maven repository) and starting the WireMock server for each of those stubs. - -=== Modules - -AccuREST comes with a new structure of modules - -[source,indent=0] ----- -└── stub-runner - ├── stub-runner - ├── stub-runner-junit - ├── stub-runner-spring - └── stub-runner-spring-cloud ----- - -==== Stub Runner - -Contains core logic of Stub Runner. Gives you a main class to run Stub Runner from the command line or from Gradle. - -Here you can see a list of options with which you can run Stub Runner: - -[source,indent=0] ----- -java -jar stub-runner.jar [options...] - -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: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) ----- - -You can either produce a fat-jar and run the app like presented above. - -You can also 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` - -==== 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: - -[source,java,indent=0] ----- -@ClassRule public static AccurestRule rule = new AccurestRule() - .repoRoot("http://your.repo.com") - .downloadStub("io.codearte.accurest.stubs", "loanIssuance") - .downloadStub("io.codearte.accurest.stubs:fraudDetectionServer") - .downloadStub("io.codearte:stub1", "io.codearte:stub2:classifier", "io.codearte:stub3"); ----- - -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 -* feed the WireMock server with all JSON files that are valid WireMock definitions - -Stub Runner uses [Groovy's Grape](http://docs.groovy-lang.org/latest/html/documentation/grape.html) mechanism to download the Maven dependencies. Check their [docs](http://docs.groovy-lang.org/latest/html/documentation/grape.html) for more information. - -Since the `AccurestRule` implements the `StubFinder` it allows you to find the started stubs: - -[source,groovy,indent=0] ----- -interface StubFinder { - /** - * 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() -} ----- - -Example of usage in Spock tests: - -[source,groovy,indent=0] ----- -@ClassRule @Shared AccurestRule rule = new AccurestRule() - .repoRoot('http://your.repo.com') - .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: - -[source,java,indent=0] ----- -@ClassRule public static AccurestRule rule = new AccurestRule() - .repoRoot("http://your.repo.com") - .downloadStub("io.codearte.accurest.stubs", "loanIssuance") - .downloadStub("io.codearte.accurest.stubs:fraudDetectionServer"); - - @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. - -==== Stub Runner Spring - -If you're using Spring then you can just import the `io.codearte.accurest.stubrunner.spring.StubRunnerConfiguration` and a bean of type `StubFinder` will get registered. - -In order to find a URL and port of a given dependency you can autowire the bean in your test and call its methods: - -[source,groovy,indent=0] ----- -@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 {} -} ----- - -Check the *Common properties for JUnit and Spring* for more information on how to apply global configuration of Stub Runner. - -==== Stub Runner Spring Cloud - -If you're using Spring Cloud then it's enough to add `stub-runner-spring-cloud` on classpath and automatically a bean of type `StubFinder` will get registered. - -==== 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: - -[width="60%",frame="topbot",options="header"] -|====================== -| Property name | Default value | Description | -|stubrunner.port.range.min|10000| Minimal value of a port for a started WireMock with stubs| -|stubrunner.port.range.max|15000| Minimal value of a port for a started WireMock with stubs| -|stubrunner.stubs.repository.root|| Maven repo url. 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|| Comma separated list of Ivy notation of stubs to download| -|====================== - -= 9. 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*][][] \ No newline at end of file + - If you don't migrate to the new approach you will have your tests under `contractsDslDir`.`contractsDslDir`.*subpackage* \ No newline at end of file