diff --git a/README.adoc b/README.adoc index dc5a173a58..33f0acfdfc 100644 --- a/README.adoc +++ b/README.adoc @@ -4,29 +4,6 @@ Manual changes to this file will be lost when it is generated again. Edit the files in the src/main/asciidoc/ directory instead. //// -:branch: master -image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-contract?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/{branch}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"] -image::https://circleci.com/gh/spring-cloud/spring-cloud-contract.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-contract"] -:branch: master -:core_path: ../../.. -:plugins_path: ../../../spring-cloud-contract-tools -:converters_path: {plugins_path}/spring-cloud-contract-converters -:verifier_root_path: {core_path}/spring-cloud-contract-verifier -:contract_spec_path: {core_path}/specs/spring-cloud-contract-spec-java -:contract_spec_tests_path: {core_path}/specs/spring-cloud-contract-spec -:samples_path: {core_path}/samples -:verifier_core_path: {verifier_root_path} -:stubrunner_core_path: {core_path}/spring-cloud-contract-stub-runner -:standalone_samples_path: {samples_path}/standalone/dsl -:standalone_messaging_samples_path: {samples_path}/standalone/messaging -:standalone_pact_path: {samples_path}/standalone/pact -:standalone_restdocs_path: {samples_path}/standalone/restdocs -:tests_path: {core_path}/tests -:samples_branch: 2.2.x -:samples_url: https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/{samples_branch} -:introduction_url: ${core_path}/../../ - == Spring Cloud Contract You always need confidence when pushing new features into a new application or service in @@ -41,1521 +18,9 @@ If you prefer to learn about the project by doing some tutorials, you can check workshops under https://cloud-samples.spring.io/spring-cloud-contract-samples/workshops.html[this link]. -=== Spring Cloud Contract Verifier +== Project page -== Spring Cloud Contract Introduction - -Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. - -=== History - -Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. -It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] -from (https://github.com/Codearte[Codearte]. - -The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. - -=== Why a Contract Verifier? - -Assume that we have a system consisting of multiple microservices: - -image::{github-raw}/docs/src/main/asciidoc/images/Deps.png[Microservices Architecture] - -==== Testing issues - -If we wanted to test the application in top left corner to determine whether it can -communicate with other services, 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. - -*Deploy all microservices and perform end to end tests* - - -Advantages: - -- Simulates production. -- Tests real communication between services. - -Disadvantages: - -- To test one microservice, we have to deploy 6 microservices, a couple of databases, -etc. -- The environment where the tests run is locked for a single suite of tests (nobody else -would be able to run the tests in the meantime). -- They take a long time to run. -- The feedback comes very late in the process. -- They are extremely hard to debug. - -*Mock other microservices in unit/integration tests* - -Advantages: - -- They provide very fast feedback. -- They have no infrastructure requirements. - -Disadvantages: - -- The implementor of the service creates stubs that might have nothing to do with -reality. -- You can go to production with passing tests and failing production. - -To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the -whole world of microservices. If you work on stubs, then the only applications you need -are those that your application directly uses. - -image::{github-raw}/docs/src/main/asciidoc/images/Stubs2.png[Stubbed Services] - -Spring Cloud Contract gives you the certainty that the stubs that you use 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 short, you can trust those stubs. - -=== Purposes - -The main purposes of Spring Cloud Contract are: - -- To ensure that Http / Messaging stubs (used when developing the client) do exactly -what the actual server-side implementation does. -- To promote ATDD (acceptance test driven developement) 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 to be used on the server side. - -By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. - -IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business -features in the contracts. Assume that we have a business use case of fraud check. If a -user can be a fraud for 100 different reasons, we would assume that you would create 2 -contracts, one for the positive case and one for the negative case. Contract tests are -used to test contracts between applications and not to simulate full behavior. - -=== How It Works - -This section explores how Spring Cloud Contract. - -[[spring-cloud-contract-verifier-intro-three-second-tour]] -==== A Three-second Tour - -This very brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find a somewhat longer tour -<>. - -[plantuml, three-second, png] ----- -"API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin -"API Producer"->"API Producer": add SCC Verifier dependency -"API Producer"->"API Producer": define contracts -"API Producer"->"Build": run build -"Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) -"Build"->"Nexus / Artifactory": upload contracts \nand stubs and the project arifact -"Build"->"API Producer": Build successful -"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency -"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Nexus / Artifactory": test asks for [API Producer] stubs -"Nexus / Artifactory"->"SCC Stub Runner": fetch the [API Producer] stubs -"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs -"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub -"SCC Stub Runner"->"API Consumer": communication is correct ----- - -[[spring-cloud-contract-verifier-intro-three-second-tour-producer]] -===== On the Producer Side - -To start working with Spring Cloud Contract, add files with `REST`/messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -Then add the Spring Cloud Contract Verifier dependency and plugin to your build file, as -shown in the following example: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the tests get generated under -`org.springframework.cloud.contract.verifier.tests.`. - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of either handling HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests, and it -should contain all the setup necessary to run them (for example `RestAssuredMockMvc` -controller setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -The changes can now be merged, and both the application and the stub artifacts may be -published in an online repository. - -[[spring-cloud-contract-verifier-intro-three-second-tour-consumer]] -===== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To do so, add the dependency to `Spring Cloud Contract Stub Runner`, as shown in the -following example: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the stubs -by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -TIP: The tests are being skipped because the Producer-side contract implementation is not -in place yet, so the automatically-generated contract tests fail. -* By getting already-existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URL as `Spring Cloud Contract -Stub Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` values for `Spring Cloud Contract Stub Runner` to -run the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -Now, in your integration test, you can receive stubbed versions of HTTP responses or -messages that are expected to be emitted by the collaborator service. - -[[spring-cloud-contract-verifier-intro-three-minute-tour]] -==== A Three-minute Tour - -This brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find an even more brief tour -<>. - -[[spring-cloud-contract-verifier-intro-three-minute-tour-producer]] -===== On the Producer Side - -To start working with `Spring Cloud Contract`, add files with `REST/` messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -For the HTTP stubs, a contract defines what kind of response should be returned for a -given request (taking into account the HTTP methods, URLs, headers, status codes, and so -on). The following example shows how an HTTP stub contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -package contracts - -org.springframework.cloud.contract.spec.Contract.make { - request { - method 'PUT' - url '/fraudcheck' - body([ - "client.id": $(regex('[0-9]{10}')), - loanAmount: 99999 - ]) - headers { - contentType('application/json') - } - } - response { - status OK() - body([ - fraudCheckStatus: "FRAUD", - "rejection.reason": "Amount too high" - ]) - headers { - contentType('application/json') - } - } -} ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -request: - method: PUT - url: /fraudcheck - body: - "client.id": 1234567890 - loanAmount: 99999 - headers: - Content-Type: application/json - matchers: - body: - - path: $.['client.id'] - type: by_regex - value: "[0-9]{10}" -response: - status: 200 - body: - fraudCheckStatus: "FRAUD" - "rejection.reason": "Amount too high" - headers: - Content-Type: application/json;charset=UTF-8 ----- -==== - -In the case of messaging, you can define: - -* The input and the output messages can be defined (taking into account from and where it -was sent, the message body, and the header). -* The methods that should be called after the message is received. -* The methods that, when called, should trigger a message. - -The following example shows a Camel messaging contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] ----- -==== - - -Then you can add Spring Cloud Contract Verifier dependency and plugin to your build file, -as shown in the following example: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the generated tests are under -`org.springframework.cloud.contract.verifier.tests.`. - -The following example shows a sample auto-generated test for an HTTP contract: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -The preceding example uses Spring's `MockMvc` to run the tests. This is the default test -mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be -used. (To do so, change the `testMode` property of the plugin to `JAX-RS` or `EXPLICIT`, -respectively.) - -Since 2.1.0, it is also possible to use `RestAssuredWebTestClient`with Spring's reactive `WebTestClient` -run under the hood. This is particularly recommended while working with Reactive, `Web-Flux`-based applications. -In order to use `WebTestClient` set `testMode` to `WEBTESTCLIENT`. - -Here is an example of a test generated in `WEBTESTCLIENT` test mode: - -[source,java,indent=0] ----- -@Test - public void validate_shouldRejectABeerIfTooYoung() throws Exception { - // given: - WebTestClientRequestSpecification request = given() - .header("Content-Type", "application/json") - .body("{\"age\":10}"); - - // when: - WebTestClientResponse response = given().spec(request) - .post("/check"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK"); - } ----- - -Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin -`testFramework` property to either `JUNIT5` or `Spock`. - -TIP: You can now also generate WireMock scenarios based on the contracts, by including an -order number followed by an underscore at the beginning of the contract file names. - -The following example shows an auto-generated test in Spock for a messaging stub contract: - -[source,groovy,indent=0] ----- -given: - ContractVerifierMessage inputMessage = contractVerifierMessaging.create( - \'\'\'{"bookName":"foo"}\'\'\', - ['sample': 'header'] - ) - -when: - contractVerifierMessaging.send(inputMessage, 'jms:delete') - -then: - noExceptionThrown() - bookWasDeleted() ----- - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of handling either HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests and should -contain all the setup necessary to run them (for example, `RestAssuredMockMvc` controller -setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -Information about installing the stubs jar to the local repository appears in the logs, as -shown in the following example: - -[source,bash,indent=0] ----- - [INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar - [INFO] - [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar - [INFO] - [INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- - [INFO] - [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar - [INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -You can now merge the changes and publish both the application and the stub artifacts -in an online repository. - -*Docker Project* - -In order to enable working with contracts while creating applications in non-JVM -technologies, the `springcloud/spring-cloud-contract` Docker image has been created. It -contains a project that automatically generates tests for HTTP contracts and executes them -in `EXPLICIT` test mode. Then, if the tests pass, it generates Wiremock stubs and, -optionally, publishes them to an artifact manager. In order to use the image, you can -mount the contracts into the `/contracts` directory and set a few environment variables. -// TODO: We should answer the obvious question: Which environment variables? - -[[spring-cloud-contract-verifier-intro-three-minute-tour-consumer]] -===== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To get started, add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the -stubs by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -NOTE: The tests are skipped because the Producer-side contract implementation is not yet -in place, so the automatically-generated contract tests fail. -* Getting already existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URl as `Spring Cloud Contract Stub -Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` for `Spring Cloud Contract Stub Runner` to run -the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -In your integration test, you can receive stubbed versions of HTTP responses or messages -that are expected to be emitted by the collaborator service. You can see entries similar -to the following in the build logs: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -==== Defining the Contract - -As consumers of services, we need to define what exactly we want to achieve. We need to -formulate our expectations. That is why we write contracts. - -Assume that you want to send a request containing the ID of a client company and the -amount it wants to borrow from us. You also want to send it to the /fraudcheck url via -the PUT method. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -==== Client Side - -Spring Cloud Contract generates stubs, which you can use during client-side testing. -You get a running WireMock instance/Messaging route that simulates the service. -You would like to feed that instance with a proper stub definition. - -At some point in time, you need to send a request to the Fraud Detection service. - -[source,groovy,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] ----- - -Annotate your test class with `@AutoConfigureStubRunner`. In the annotation provide the group id and artifact id for the Stub Runner to download stubs of your collaborators. - -[source,groovy,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] ----- - -After that, during the tests, Spring Cloud Contract automatically finds the stubs -(simulating the real service) in the Maven repository and exposes them on a configured -(or random) port. - -==== Server Side - -Since you are developing your stub, you need to be sure that it actually resembles your -concrete implementation. You cannot have a situation where your stub acts in one way and -your application behaves in a different way, especially in production. - -To ensure that your application behaves the way you define in your stub, tests are -generated from the stub you provide. - -The autogenerated test looks, more or less, like this: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -=== Step-by-step Guide to Consumer Driven Contracts (CDC) - -Consider an example of Fraud Detection and the Loan Issuance process. The business -scenario is such that we want to issue loans to people but do not want them to steal from -us. The current implementation of our system grants loans to everybody. - -Assume that `Loan Issuance` is a client to the `Fraud Detection` server. In the current -sprint, we must develop a new feature: if a client wants to borrow too much money, then -we mark the client as a fraud. - -Technical remark - Fraud Detection has an `artifact-id` of `http-server`, while Loan -Issuance has an artifact-id of `http-client`, and both have a `group-id` of `com.example`. - -Social remark - both client and server development teams need to communicate directly and -discuss changes while going through the process. CDC is all about communication. - -The https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-server[server -side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-client[the -client code here]. - -TIP: In this case, the producer owns the contracts. Physically, all the contract are -in the producer's repository. - -==== Technical note - -If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the -following section to your build: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repos,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0] ----- - -==== Consumer side (Loan Issuance) - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps: - -. Start doing TDD by writing a test for your feature. -. Write the missing implementation. -. Clone the Fraud Detection service repository locally. -. Define the contract locally in the repo of Fraud Detection service. -. Add the Spring Cloud Contract Verifier plugin. -. Run the integration tests. -. File a pull request. -. Create an initial implementation. -. Take over the pull request. -. Write the missing implementation. -. Deploy your app. -. Work online. - -*Start doing TDD by writing a test for your feature.* - -[source,groovy,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0] ----- - -Assume that you have written a test of your new feature. If a loan application for a big -amount is received, the system should reject that loan application with some description. - -*Write the missing implementation.* - -At some point in time, you need to send a request to the Fraud Detection service. Assume -that you need to send the request containing the ID of the client and the amount the -client wants to borrow. You want to send it to the `/fraudcheck` url via the `PUT` method. - -[source,groovy,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] ----- - -For simplicity, the port of the Fraud Detection service is set to `8080`, and the -application runs on `8090`. - -If you start the test at this point, it breaks, because no service currently runs on port -`8080`. - -*Clone the Fraud Detection service repository locally.* - -You can start by playing around with the server side contract. To do so, you must first -clone it. - -[source,bash,indent=0] ----- -$ git clone https://your-git-server.com/server-side.git local-http-server-repo ----- - -*Define the contract locally in the repo of Fraud Detection service.* - -As a consumer, you need to define what exactly you want to achieve. You need to formulate -your expectations. To do so, write the following contract: - -IMPORTANT: Place the contract under `src/test/resources/contracts/fraud` folder. The `fraud` folder -is important because the producer's test base class name references that folder. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -The YML contract is quite straight-forward. However when you take a look at the Contract -written using a statically typed Groovy DSL - you might wonder what the -`value(client(...), server(...))` parts are. By using this notation, Spring Cloud -Contract lets you define parts of a JSON block, a URL, etc., which are dynamic. In case -of an identifier or a timestamp, you need not hardcode a value. You want to allow some -different ranges of values. To enable ranges of values, you can set regular expressions -matching those values for the consumer side. You can provide the body by means of either -a map notation or String with interpolations. -Consult the <> section for more information. We highly recommend using the map notation! - -TIP: You must understand the map notation in order to set up contracts. Please read the -https://groovy-lang.org/json.html[Groovy docs regarding JSON]. - -The previously shown contract is an agreement between two sides that: - -- if an HTTP request is sent with all of -** a `PUT` method on the `/fraudcheck` endpoint, -** a JSON body with a `client.id` that matches the regular expression `[0-9]{10}` and -`loanAmount` equal to `99999`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`, -- then an HTTP response is sent to the consumer that -** has status `200`, -** contains a JSON body with the `fraudCheckStatus` field containing a value `FRAUD` and -the `rejectionReason` field having value `Amount too high`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`. - -Once you are ready to check the API in practice in the integration tests, you need to -install the stubs locally. - -*Add the Spring Cloud Contract Verifier plugin.* - -We can add either a Maven or a Gradle plugin. In this example, you see how to add Maven. -First, add the `Spring Cloud Contract` BOM. - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_bom,indent=0] ----- - -Next, add the `Spring Cloud Contract Verifier` Maven plugin - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -Since the plugin was added, you get the `Spring Cloud Contract Verifier` features which, -from the provided contracts: - -- generate and run tests -- produce and install stubs - -You do not want to generate tests since you, as the consumer, want only to play with the -stubs. You need to skip the test generation and execution. When you execute: - -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- - -In the logs, you see something like this: - -[source,bash,indent=0] ----- -[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar -[INFO] -[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar -[INFO] -[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- -[INFO] -[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar -[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -The following line is extremely important: - -[source,bash,indent=0] ----- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -It confirms that the stubs of the `http-server` have been installed in the local -repository. - -*Run the integration tests.* - -In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic -stub downloading, you must do the following in your consumer side project (`Loan -Application service`): - -Add the `Spring Cloud Contract` BOM: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=contract_bom,indent=0] ----- - -Add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -Annotate your test class with `@AutoConfigureStubRunner`. In the annotation, provide the -`group-id` and `artifact-id` for the Stub Runner to download the stubs of your -collaborators. (Optional step) Because you're playing with the collaborators offline, you -can also provide the offline work switch (`StubRunnerProperties.StubsMode.LOCAL`). - -[source,groovy,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] ----- - -Now, when you run your tests, you see something like this: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -This output means that Stub Runner has found your stubs and started a server for your app -with group id `com.example`, artifact id `http-server` with version `0.0.1-SNAPSHOT` of -the stubs and with `stubs` classifier on port `8080`. - -*File a pull request.* - -What you have done until now is an iterative process. You can play around with the -contract, install it locally, and work on the consumer side until the contract works as -you wish. - -Once you are satisfied with the results and the test passes, publish a pull request to -the server side. Currently, the consumer side work is done. - -==== Producer side (Fraud Detection server) - -As a developer of the Fraud Detection server (a server to the Loan Issuance service): - -*Create an initial implementation.* - -As a reminder, you can see the initial implementation here: - -[source,java,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -*Take over the pull request.* - -[source,bash,indent=0] ----- -$ git checkout -b contract-change-pr master -$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr ----- - -You must add the dependencies needed by the autogenerated tests: - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -In the configuration of the Maven plugin, pass the `packageWithBaseClasses` property - -[source,xml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -IMPORTANT: This example uses "convention based" naming by setting the -`packageWithBaseClasses` property. Doing so means that the two last packages combine to -make the name of the base test class. In our case, the contracts were placed under -`src/test/resources/contracts/fraud`. Since you do not have two packages starting from -the `contracts` folder, pick only one, which should be `fraud`. Add the `Base` suffix and -capitalize `fraud`. That gives you the `FraudBase` test class name. - -All the generated tests extend that class. Over there, you can set up your Spring Context -or whatever is necessary. In this case, use https://github.com/rest-assured/rest-assured[Rest Assured MVC] to -start the server side `FraudDetectionController`. - -[source,java,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/test/java/com/example/fraud/FraudBase.java[] ----- - -Now, if you run the `./mvnw clean install`, you get something like this: - -[source,bash,indent=0] ----- -Results : - -Tests in error: - ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed... ----- - -This error occurs because you have a new contract from which a test was generated and it -failed since you have not implemented the feature. The auto-generated test would look -like this: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -If you used the Groovy DSL, you can see, all the `producer()` parts of the Contract that were present in the -`value(consumer(...), producer(...))` blocks got injected into the test. -In case of using YAML, the same applied for the `matchers` sections of the `response`. - -Note that, on the producer side, you are also doing TDD. The expectations are expressed -in the form of a test. This test sends a request to our own application with the URL, -headers, and body defined in the contract. It also is expecting precisely defined values -in the response. In other words, you have the `red` part of `red`, `green`, and -`refactor`. It is time to convert the `red` into the `green`. - -*Write the missing implementation.* - -Because you know the expected input and expected output, you can write the missing -implementation: - -[source,java,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0] -Unresolved directive in _verifier_how_it_works.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -When you execute `./mvnw clean install` again, the tests pass. Since the `Spring Cloud -Contract Verifier` plugin adds the tests to the `generated-test-sources`, you can -actually run those tests from your IDE. - -*Deploy your app.* - -Once you finish your work, you can deploy your change. First, merge the branch: - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr -$ git push origin master ----- - -Your CI might run something like `./mvnw clean deploy`, which would publish both the -application and the stub artifacts. - -==== Consumer Side (Loan Issuance) Final Step - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server): - -*Merge branch to master.* - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr ----- - -*Work online.* - -Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate -where the repository with your stubs is located. At this moment the stubs of the server -side are automatically downloaded from Nexus/Artifactory. You can set the value of -`stubsMode` to `REMOTE`. The following code shows an example of -achieving the same thing by changing the properties. - -[source,yaml,indent=0] ----- -Unresolved directive in _verifier_how_it_works.adoc - include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -That's it! - -=== Dependencies - -The best way to add dependencies is to use the proper `starter` dependency. - -For `stub-runner`, use `spring-cloud-starter-stub-runner`. When you use a plugin, add -`spring-cloud-starter-contract-verifier`. - -=== Additional Links - -Here are some resources related to Spring Cloud Contract Verifier and Stub Runner. Note -that some may be outdated, because the Spring Cloud Contract Verifier project is under -constant development. - -==== Spring Cloud Contract video - -You can check out the video from the Warsaw JUG about Spring Cloud Contract: - -video::sAAklvxmPmk[youtube,start=538,width=640,height=480] - -==== Readings - -- https://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest] -- https://toomuchcoding.com/blog/categories/accurest/[Accurest related articles from Marcin Grzejszczak's blog] -- https://toomuchcoding.com/blog/categories/spring-cloud-contract/[Spring Cloud Contract related articles from Marcin Grzejszczak's blog] -- https://groovy-lang.org/json.html[Groovy docs regarding JSON] - -=== Samples - -You can find some samples at -https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. - -== Links - -The following links may be helpful when working with Spring Cloud Contract: - -* https://github.com/spring-cloud/spring-cloud-contract/[Spring Cloud Contract Github -Repository] -* https://github.com/spring-cloud-samples/spring-cloud-contract-samples/[Spring Cloud -Contract Samples] -* https://gitter.im/spring-cloud/spring-cloud-contract[Spring Cloud Contract Gitter] -* https://www.youtube.com/watch?v=sAAklvxmPmk[Spring Cloud Contract WJUG Presentation by -Marcin Grzejszczak] - -=== Spring Cloud Contract WireMock - -:core_path: ../../.. -:doc_samples: {core_path}/samples/wiremock-jetty -:wiremock_tests: {core_path}/spring-cloud-contract-wiremock - -== Spring Cloud Contract WireMock - -The Spring Cloud Contract WireMock modules let you use https://github.com/tomakehurst/wiremock[WireMock] in a -Spring Boot application. Check out the -https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples[samples] -for more details. - -If you have a Spring Boot application that uses Tomcat as an embedded server (which is -the default with `spring-boot-starter-web`), you can add -`spring-cloud-starter-contract-stub-runner` to your classpath and add `@AutoConfigureWireMock` in -order to be able to use Wiremock in your tests. Wiremock runs as a stub server and you -can register stub behavior using a Java API or via static JSON declarations as part of -your test. The following code shows an example: - -[source,java,indent=0] ----- -Unresolved directive in _spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test1] -Unresolved directive in _spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test2] ----- - -To start the stub server on a different port use (for example), -`@AutoConfigureWireMock(port=9999)`. For a random port, use a value of `0`. The stub -server port can be bound in the test application context with the "wiremock.server.port" -property. Using `@AutoConfigureWireMock` adds a bean of type `WiremockConfiguration` to -your test application context, where it will be cached in between methods and classes -having the same context, the same as for Spring integration tests. Also you can inject a bean of type `WireMockServer` into your test. - -=== Registering Stubs Automatically - -If you use `@AutoConfigureWireMock`, it registers WireMock JSON stubs from the file -system or classpath (by default, from `file:src/test/resources/mappings`). You can -customize the locations using the `stubs` attribute in the annotation, which can be an -Ant-style resource pattern or a directory. In the case of a directory, `**/*.json` is -appended. The following code shows an example: - ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWireMock(stubs="classpath:/stubs") -public class WiremockImportApplicationTests { - - @Autowired - private Service service; - - @Test - public void contextLoads() throws Exception { - assertThat(this.service.go()).isEqualTo("Hello World!"); - } - -} ----- - -NOTE: Actually, WireMock always loads mappings from `src/test/resources/mappings` *as -well as* the custom locations in the stubs attribute. To change this behavior, you can -also specify a files root as described in the next section of this document. - -If you're using Spring Cloud Contract's default stub jars, then your -stubs are stored under `/META-INF/group-id/artifact-id/versions/mappings/` folder. If you want to register all stubs from that location, from all embedded JARs, then it's enough to use the following syntax. - -[source,java,indent=0] ----- -Unresolved directive in _spring-cloud-wiremock.adoc - include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockFilesApplicationWithUrlResourceTests.java[tags=load_all_stubs] ----- - -=== Using Files to Specify the Stub Bodies - -WireMock can read response bodies from files on the classpath or the file system. In that -case, you can see in the JSON DSL that the response has a `bodyFileName` instead of a -(literal) `body`. The files are resolved relative to a root directory (by default, -`src/test/resources/\__files`). To customize this location you can set the `files` -attribute in the `@AutoConfigureWireMock` annotation to the location of the parent -directory (in other words, `__files` is a subdirectory). You can use Spring resource -notation to refer to `file:...` or `classpath:...` locations. Generic URLs are not -supported. A list of values can be given, in which case WireMock resolves the first file -that exists when it needs to find a response body. - -NOTE: When you configure the `files` root, it also affects the -automatic loading of stubs, because they come from the root location -in a subdirectory called "mappings". The value of `files` has no -effect on the stubs loaded explicitly from the `stubs` attribute. - -=== Alternative: Using JUnit Rules - -For a more conventional WireMock experience, you can use JUnit `@Rules` to start and stop -the server. To do so, use the `WireMockSpring` convenience class to obtain an `Options` -instance, as shown in the following example: - -[source,java,indent=0] ----- -Unresolved directive in _spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test1] -Unresolved directive in _spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test2] ----- - -The `@ClassRule` means that the server shuts down after all the methods in this class -have been run. - -=== Relaxed SSL Validation for Rest Template - -WireMock lets you stub a "secure" server with an "https" URL protocol. If your -application wants to contact that stub server in an integration test, it will find that -the SSL certificates are not valid (the usual problem with self-installed certificates). -The best option is often to re-configure the client to use "http". If that's not an -option, you can ask Spring to configure an HTTP client that ignores SSL validation errors -(do so only for tests, of course). - -To make this work with minimum fuss, you need to be using the Spring Boot -`RestTemplateBuilder` in your app, as shown in the following example: - -[source,java,indent=0] ----- - @Bean - public RestTemplate restTemplate(RestTemplateBuilder builder) { - return builder.build(); - } ----- - -You need `RestTemplateBuilder` because the builder is passed through callbacks to -initialize it, so the SSL validation can be set up in the client at that point. This -happens automatically in your test if you are using the `@AutoConfigureWireMock` -annotation or the stub runner. If you use the JUnit `@Rule` approach, you need to add the -`@AutoConfigureHttpClient` annotation as well, as shown in the following example: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest("app.baseUrl=https://localhost:6443") -@AutoConfigureHttpClient -public class WiremockHttpsServerApplicationTests { - - @ClassRule - public static WireMockClassRule wiremock = new WireMockClassRule( - WireMockSpring.options().httpsPort(6443)); -... -} ----- - -If you are using `spring-boot-starter-test`, you have the Apache HTTP client on the -classpath and it is selected by the `RestTemplateBuilder` and configured to ignore SSL -errors. If you use the default `java.net` client, you do not need the annotation (but it -won't do any harm). There is no support currently for other clients, but it may be added -in future releases. - -To disable the custom `RestTemplateBuilder`, set the `wiremock.rest-template-ssl-enabled` -property to `false`. - -=== WireMock and Spring MVC Mocks - -Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into -a Spring `MockRestServiceServer`. The following code shows an example: - -[source,java,indent=0] ----- -Unresolved directive in _spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java[tags=wiremock_test] ----- - -The `baseUrl` value is prepended to all mock calls, and the `stubs()` method takes a stub -path resource pattern as an argument. In the preceding example, the stub defined at -`/stubs/resource.json` is loaded into the mock server. If the `RestTemplate` is asked to -visit `https://example.org/`, it gets the responses as being declared at that URL. More -than one stub pattern can be specified, and each one can be a directory (for a recursive -list of all ".json"), a fixed filename (as in the example above), or an Ant-style -pattern. The JSON format is the normal WireMock format, which you can read about in the -https://wiremock.org/docs/stubbing/[WireMock website]. - -Currently, the Spring Cloud Contract Verifier supports Tomcat, Jetty, and Undertow as -Spring Boot embedded servers, and Wiremock itself has "native" support for a particular -version of Jetty (currently 9.2). To use the native Jetty, you need to add the native -Wiremock dependencies and exclude the Spring Boot container (if there is one). - -=== Customization of WireMock configuration - -You can register a bean of `org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer` type -in order to customize the WireMock configuration (e.g. add custom transformers). -Example: - -[source,java,indent=0] ----- -Unresolved directive in _spring-cloud-wiremock.adoc - include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_1] -// perform your customization here -Unresolved directive in _spring-cloud-wiremock.adoc - include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_2] ----- - -=== Generating Stubs using REST Docs - -https://projects.spring.io/spring-restdocs[Spring REST Docs] can be used to generate -documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc -or `WebTestClient` or Rest Assured. At the same time that you generate documentation for your API, you can also -generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your -normal REST Docs test cases and use `@AutoConfigureRestDocs` to have stubs be -automatically generated in the REST Docs output directory. - -[plantuml, rest-docs, png] ----- -"API Producer"->"API Producer": add Spring Cloud Contract (SCC) \nStub Runner dependency -"API Producer"->"API Producer": setup stub jar assembly -"API Producer"->"API Producer": write and setup REST Docs tests -"API Producer"->"Build": run build -"Build"->"REST Docs": generate API \ndocumentation -"REST Docs"->"SCC": generate stubs from the \nREST Docs tests -"REST Docs"->"SCC": generate contracts from the \nREST Docs tests -"Build"->"Build": assemble stubs jar with \nstubs and contracts -"Build"->"Nexus / Artifactory": upload contracts \nand stubs and the project arifact -"Build"->"API Producer": Build successful -"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency -"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Nexus / Artifactory": test asks for [API Producer] stubs -"Nexus / Artifactory"->"SCC Stub Runner": fetch the [API Producer] stubs -"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs -"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub -"SCC Stub Runner"->"API Consumer": communication is correct ----- - -The following code shows an -example using `MockMvc`: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureMockMvc -public class ApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void contextLoads() throws Exception { - mockMvc.perform(get("/resource")) - .andExpect(content().string("Hello World")) - .andDo(document("resource")); - } -} ----- - -This test generates a WireMock stub at "target/snippets/stubs/resource.json". It matches -all GET requests to the "/resource" path. The same example with `WebTestClient` (used -for testing Spring WebFlux applications) would look like this: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureWebTestClient -public class ApplicationTests { - - @Autowired - private WebTestClient client; - - @Test - public void contextLoads() throws Exception { - client.get().uri("/resource").exchange() - .expectBody(String.class).isEqualTo("Hello World") - .consumeWith(document("resource")); - } -} ----- - -Without any additional configuration, these tests create a stub with a request matcher -for the HTTP method and all headers except "host" and "content-length". To match the -request more precisely (for example, to match the body of a POST or PUT), we need to -explicitly create a request matcher. Doing so has two effects: - -* Creating a stub that matches only in the way you specify. -* Asserting that the request in the test case also matches the same conditions. - -The main entry point for this feature is `WireMockRestDocs.verify()`, which can be used -as a substitute for the `document()` convenience method, as shown in the following -example: - -[source,java,indent=0] - -import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify; - ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureMockMvc -public class ApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void contextLoads() throws Exception { - mockMvc.perform(post("/resource") - .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) - .andExpect(status().isOk()) - .andDo(verify().jsonPath("$.id") - .stub("resource")); - } -} ----- - -This contract specifies that any valid POST with an "id" field receives the response -defined in this test. You can chain together calls to `.jsonPath()` to add additional -matchers. If JSON Path is unfamiliar, The https://github.com/jayway/JsonPath[JayWay -documentation] can help you get up to speed. The `WebTestClient` version of this test -has a similar `verify()` static helper that you insert in the same place. - -Instead of the `jsonPath` and `contentType` convenience methods, you can also use the -WireMock APIs to verify that the request matches the created stub, as shown in the -following example: - -[source,java,indent=0] ----- - @Test - public void contextLoads() throws Exception { - mockMvc.perform(post("/resource") - .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) - .andExpect(status().isOk()) - .andDo(verify() - .wiremock(WireMock.post( - urlPathEquals("/resource")) - .withRequestBody(matchingJsonPath("$.id")) - .stub("post-resource")); - } ----- - -The WireMock API is rich. You can match headers, query parameters, and request body by -regex as well as by JSON path. These features can be used to create stubs with a wider -range of parameters. The above example generates a stub resembling the following example: - -.post-resource.json -[source,json] ----- -{ - "request" : { - "url" : "/resource", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$.id" - }] - }, - "response" : { - "status" : 200, - "body" : "Hello World", - "headers" : { - "X-Application-Context" : "application:-1", - "Content-Type" : "text/plain" - } - } -} ----- - -NOTE: You can use either the `wiremock()` method or the `jsonPath()` and `contentType()` -methods to create request matchers, but you can't use both approaches. - -On the consumer side, you can make the `resource.json` generated earlier in this section -available on the classpath (by -<= 20)]', byType()) - } - } -} ----- - -The generated document (formatted in Asciidoc in this case) contains a formatted -contract. The location of this file would be `index/dsl-contract.adoc`. - -== Documentation - -You can read more about Spring Cloud Contract Verifier by reading the -{documentation_url}[docs] +You can read more about Spring Cloud Contract by going to https://spring.io/projects/spring-cloud-contract[the project page] == Contributing diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 2c6fded0b4..0286658de7 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -1,26 +1,3 @@ -:branch: master -image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-contract?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-contract/branch/{branch}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-contract"] -image::https://circleci.com/gh/spring-cloud/spring-cloud-contract.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-contract"] -:branch: master -:core_path: ../../.. -:plugins_path: ../../../spring-cloud-contract-tools -:converters_path: {plugins_path}/spring-cloud-contract-converters -:verifier_root_path: {core_path}/spring-cloud-contract-verifier -:contract_spec_path: {core_path}/specs/spring-cloud-contract-spec-java -:contract_spec_tests_path: {core_path}/specs/spring-cloud-contract-spec -:samples_path: {core_path}/samples -:verifier_core_path: {verifier_root_path} -:stubrunner_core_path: {core_path}/spring-cloud-contract-stub-runner -:standalone_samples_path: {samples_path}/standalone/dsl -:standalone_messaging_samples_path: {samples_path}/standalone/messaging -:standalone_pact_path: {samples_path}/standalone/pact -:standalone_restdocs_path: {samples_path}/standalone/restdocs -:tests_path: {core_path}/tests -:samples_branch: 2.2.x -:samples_url: https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/{samples_branch} -:introduction_url: ${core_path}/../../ - == Spring Cloud Contract You always need confidence when pushing new features into a new application or service in @@ -35,20 +12,9 @@ If you prefer to learn about the project by doing some tutorials, you can check workshops under https://cloud-samples.spring.io/spring-cloud-contract-samples/workshops.html[this link]. -=== Spring Cloud Contract Verifier +== Project page -include::_verifier_introduction.adoc[] - -include::_links.adoc[] - -=== Spring Cloud Contract WireMock - -include::_spring-cloud-wiremock.adoc[] - -== Documentation - -You can read more about Spring Cloud Contract Verifier by reading the -{documentation_url}[docs] +You can read more about Spring Cloud Contract by going to https://spring.io/projects/spring-cloud-contract[the project page] == Contributing diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/src/main/asciidoc/_attributes.adoc index 4560b15951..868f5ebdea 100644 --- a/docs/src/main/asciidoc/_attributes.adoc +++ b/docs/src/main/asciidoc/_attributes.adoc @@ -32,5 +32,6 @@ :samples_branch: 2.2.x :samples_url: https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/{samples_branch} :samples_code: https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/ - +:doc_samples: {core_path}/samples/wiremock-jetty +:wiremock_tests: {core_path}/spring-cloud-contract-wiremock :introduction_url: {core_path} \ No newline at end of file diff --git a/docs/src/main/asciidoc/_verifier_how_it_works.adoc b/docs/src/main/asciidoc/_getting-started.adoc similarity index 69% rename from docs/src/main/asciidoc/_verifier_how_it_works.adoc rename to docs/src/main/asciidoc/_getting-started.adoc index ca5b39e898..50ed913b7c 100644 --- a/docs/src/main/asciidoc/_verifier_how_it_works.adoc +++ b/docs/src/main/asciidoc/_getting-started.adoc @@ -1,38 +1,157 @@ -=== How It Works +include::_attributes.adoc[] -This section explores how Spring Cloud Contract. +If you are getting started with {project-full-name}, or "`Spring`" in general, start by reading +this section. It answers the basic "`what?`", "`how?`" and "`why?`" questions. It +includes an introduction to {project-full-name}, along with installation instructions. We then +walk you through building your first {project-full-name} application, discussing some core +principles as we go. -[[spring-cloud-contract-verifier-intro-three-second-tour]] -==== A Three-second Tour +[[getting-started-introducing-spring-cloud-contract]] +== Introducing Spring Cloud Contract + +Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. + +[[getting-started-introducing-spring-cloud-contract-history]] +=== History + +Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. +It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] +from (https://github.com/Codearte[Codearte]). + +The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. + +[[getting-started-introducing-spring-cloud-contract-why]] +==== Why would you need it? + +Assume that we have a system consisting of multiple microservices: + +image::images/Deps.png[Microservices Architecture] + +[[getting-started-introducing-spring-cloud-contract-testing-issues]] +==== Testing issues + +If we wanted to test the application in top left corner to determine whether it can +communicate with other services, 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. + +*Deploy all microservices and perform end to end tests* + +Advantages: + +- Simulates production. +- Tests real communication between services. + +Disadvantages: + +- To test one microservice, we have to deploy 6 microservices, a couple of databases, +etc. +- The environment where the tests run is locked for a single suite of tests (nobody else +would be able to run the tests in the meantime). +- They take a long time to run. +- The feedback comes very late in the process. +- They are extremely hard to debug. + +*Mock other microservices in unit/integration tests* + +Advantages: + +- They provide very fast feedback. +- They have no infrastructure requirements. + +Disadvantages: + +- The implementor of the service creates stubs that might have nothing to do with +reality. +- You can go to production with passing tests and failing production. + +To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the +whole world of microservices. If you work on stubs, then the only applications you need +are those that your application directly uses. + +image::images/Stubs2.png[Stubbed Services] + +Spring Cloud Contract gives you the certainty that the stubs that you use 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 short, you can trust those stubs. + +[[getting-started-introducing-spring-cloud-contract-purposes]] +=== Purposes + +The main purposes of Spring Cloud Contract are: + +- To ensure that HTTP / Messaging stubs (used when developing the client) do exactly +what the actual server-side implementation does. +- To promote ATDD (acceptance test driven developement) 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 to be used on the server side. + +By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. + +IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business +features in the contracts. Assume that we have a business use case of fraud check. If a +user can be a fraud for 100 different reasons, we would assume that you would create 2 +contracts, one for the positive case and one for the negative case. Contract tests are +used to test contracts between applications and not to simulate full behavior. + +[[getting-started-what-is-a-contract]] +=== What is a contract? + +As consumers of services, we need to define what exactly we want to achieve. We need to +formulate our expectations. That is why we write contracts. In other words a contract is an agreement on how the API / message communication should look like. Let's look at the following example + +Assume that you want to send a request containing the ID of a client company and the +amount it wants to borrow from us. You also want to send it to the /fraudcheck url via +the PUT method. + +==== +[source,groovy,indent=0,role="primary"] +.groovy +---- +include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] +---- + +[source,yaml,indent=0,role="secondary"] +.yaml +---- +include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] +---- +==== + +[[getting-started-three-second-tour]] +== A Three-second Tour This very brief tour walks through using Spring Cloud Contract: -* <> -* <> +* <> +* <> You can find a somewhat longer tour -<>. +<>. -[plantuml, three-second, png] +[plantuml, getting-started-three-second, png] ---- "API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin "API Producer"->"API Producer": add SCC Verifier dependency "API Producer"->"API Producer": define contracts "API Producer"->"Build": run build "Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) -"Build"->"Nexus / Artifactory": upload contracts \nand stubs and the project arifact +"Build"->"Stub Storage": upload contracts \nand stubs and the project arifact "Build"->"API Producer": Build successful "API Consumer"->"API Consumer": add SCC Stub Runner \ndependency "API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Nexus / Artifactory": test asks for [API Producer] stubs -"Nexus / Artifactory"->"SCC Stub Runner": fetch the [API Producer] stubs +"SCC Stub Runner"->"Stub Storage": test asks for [API Producer] stubs +"Stub Storage"->"SCC Stub Runner": fetch the [API Producer] stubs "SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs "API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub "SCC Stub Runner"->"API Consumer": communication is correct ---- -[[spring-cloud-contract-verifier-intro-three-second-tour-producer]] -===== On the Producer Side +[[getting-started-three-second-tour-producer]] +=== On the Producer Side To start working with Spring Cloud Contract, add files with `REST`/messaging contracts expressed in either Groovy DSL or YAML to the contracts directory, which is set by the @@ -77,8 +196,8 @@ application and the stub artifacts are built and installed in the local Maven re The changes can now be merged, and both the application and the stub artifacts may be published in an online repository. -[[spring-cloud-contract-verifier-intro-three-second-tour-consumer]] -===== On the Consumer Side +[[getting-started-three-second-tour-consumer]] +=== On the Consumer Side `Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running WireMock instance or messaging route that simulates the actual service. @@ -110,7 +229,7 @@ Stub Runner` properties, as shown in the following example: + [source,yaml,indent=0] ---- -include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ---- Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, @@ -132,21 +251,46 @@ TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online reposito Now, in your integration test, you can receive stubbed versions of HTTP responses or messages that are expected to be emitted by the collaborator service. -[[spring-cloud-contract-verifier-intro-three-minute-tour]] -==== A Three-minute Tour +[[getting-started-first-application]] +== Developing Your First Spring Cloud Contract based application This brief tour walks through using Spring Cloud Contract: -* <> -* <> +* <> +* <> You can find an even more brief tour -<>. +<>. -[[spring-cloud-contract-verifier-intro-three-minute-tour-producer]] -===== On the Producer Side +For the sake of this example the `Stub Storage` will be Nexus / Artifactory. -To start working with `Spring Cloud Contract`, add files with `REST/` messaging contracts +image::getting-started-three-second.png[Getting started first application] + +[[getting-started-first-application-producer]] +=== On the Producer Side + +To start working with `Spring Cloud Contract`, add Spring Cloud Contract Verifier dependency and plugin to your build file, +as shown in the following example: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] +---- + +The following listing shows how to add the plugin, which should go in the build/plugins +portion of the file: + +[source,xml,indent=0] +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + +---- + +Now let's add files with `REST/` messaging contracts expressed in either Groovy DSL or YAML to the contracts directory, which is set by the `contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. @@ -234,35 +378,22 @@ include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3. ---- ==== - -Then you can add Spring Cloud Contract Verifier dependency and plugin to your build file, -as shown in the following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - Running `./mvnw clean install` automatically generates tests that verify the application compliance with the added contracts. By default, the generated tests are under `org.springframework.cloud.contract.verifier.tests.`. -The following example shows a sample auto-generated test for an HTTP contract: +The generated tests will differ, depending on which framework and test type you've setup in your plugin. -[source,java,indent=0] +In the samples below you'll find: + +- the default test mode for HTTP contracts via `MockMvc` +- JAX-RS client via the `JAXRS` test mode +- `WebTestClient` based test (this is particularly recommended while working with Reactive, `Web-Flux`-based applications) set via `WEBTESTCLIENT` test mode +- Spock based test via the `testFramework` property set to `SPOCK` + +==== +[source,java,indent=0,role="primary"] +.mockmvc ---- @Test public void validate_shouldMarkClientAsFraud() throws Exception { @@ -285,18 +416,45 @@ public void validate_shouldMarkClientAsFraud() throws Exception { } ---- -The preceding example uses Spring's `MockMvc` to run the tests. This is the default test -mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be -used. (To do so, change the `testMode` property of the plugin to `JAX-RS` or `EXPLICIT`, -respectively.) +[source,java,indent=0,role="secondary"] +.jaxrs +---- +@SuppressWarnings("rawtypes") +public class FooTest { + WebTarget webTarget; -Since 2.1.0, it is also possible to use `RestAssuredWebTestClient`with Spring's reactive `WebTestClient` -run under the hood. This is particularly recommended while working with Reactive, `Web-Flux`-based applications. -In order to use `WebTestClient` set `testMode` to `WEBTESTCLIENT`. + @Test + public void validate_() throws Exception { -Here is an example of a test generated in `WEBTESTCLIENT` test mode: + // 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() + .build("GET") + .invoke(); + String responseAsString = response.readEntity(String.class); -[source,java,indent=0] + // then: + assertThat(response.getStatus()).isEqualTo(200); + + // and: + DocumentContext parsedJson = JsonPath.parse(responseAsString); + assertThatJson(parsedJson).field("['property1']").isEqualTo("a"); + } + +} +---- + +[source,java,indent=0,role="secondary"] +.webtestclient ---- @Test public void validate_shouldRejectABeerIfTooYoung() throws Exception { @@ -318,15 +476,8 @@ Here is an example of a test generated in `WEBTESTCLIENT` test mode: } ---- -Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin -`testFramework` property to either `JUNIT5` or `Spock`. - -TIP: You can now also generate WireMock scenarios based on the contracts, by including an -order number followed by an underscore at the beginning of the contract file names. - -The following example shows an auto-generated test in Spock for a messaging stub contract: - -[source,groovy,indent=0] +[source,groovy,indent=0,role="secondary"] +.spock ---- given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( @@ -341,6 +492,7 @@ then: noExceptionThrown() bookWasDeleted() ---- +==== As the implementation of the functionalities described by the contracts is not yet present, the tests fail. @@ -375,18 +527,8 @@ shown in the following example: You can now merge the changes and publish both the application and the stub artifacts in an online repository. -*Docker Project* - -In order to enable working with contracts while creating applications in non-JVM -technologies, the `springcloud/spring-cloud-contract` Docker image has been created. It -contains a project that automatically generates tests for HTTP contracts and executes them -in `EXPLICIT` test mode. Then, if the tests pass, it generates Wiremock stubs and, -optionally, publishes them to an artifact manager. In order to use the image, you can -mount the contracts into the `/contracts` directory and set a few environment variables. -// TODO: We should answer the obvious question: Which environment variables? - -[[spring-cloud-contract-verifier-intro-three-minute-tour-consumer]] -===== On the Consumer Side +[[getting-started-first-application-consumer]] +=== On the Consumer Side `Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running WireMock instance or messaging route that simulates the actual service. @@ -417,7 +559,7 @@ Runner` properties, as shown in the following example: + [source,yaml,indent=0] ---- -include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ---- Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, @@ -451,88 +593,8 @@ to the following in the build logs: 2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ---- -==== Defining the Contract - -As consumers of services, we need to define what exactly we want to achieve. We need to -formulate our expectations. That is why we write contracts. - -Assume that you want to send a request containing the ID of a client company and the -amount it wants to borrow from us. You also want to send it to the /fraudcheck url via -the PUT method. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -==== Client Side - -Spring Cloud Contract generates stubs, which you can use during client-side testing. -You get a running WireMock instance/Messaging route that simulates the service. -You would like to feed that instance with a proper stub definition. - -At some point in time, you need to send a request to the Fraud Detection service. - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] ----- - -Annotate your test class with `@AutoConfigureStubRunner`. In the annotation provide the group id and artifact id for the Stub Runner to download stubs of your collaborators. - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] ----- - -After that, during the tests, Spring Cloud Contract automatically finds the stubs -(simulating the real service) in the Maven repository and exposes them on a configured -(or random) port. - -==== Server Side - -Since you are developing your stub, you need to be sure that it actually resembles your -concrete implementation. You cannot have a situation where your stub acts in one way and -your application behaves in a different way, especially in production. - -To ensure that your application behaves the way you define in your stub, tests are -generated from the stub you provide. - -The autogenerated test looks, more or less, like this: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -=== Step-by-step Guide to Consumer Driven Contracts (CDC) +[[getting-started-cdc]] +== Step-by-step Guide to Consumer Driven Contracts (CDC) with contracts laying on the producer side Consider an example of Fraud Detection and the Loan Issuance process. The business scenario is such that we want to issue loans to people but do not want them to steal from @@ -542,11 +604,19 @@ Assume that `Loan Issuance` is a client to the `Fraud Detection` server. In the sprint, we must develop a new feature: if a client wants to borrow too much money, then we mark the client as a fraud. -Technical remark - Fraud Detection has an `artifact-id` of `http-server`, while Loan -Issuance has an artifact-id of `http-client`, and both have a `group-id` of `com.example`. +Technical remarks -Social remark - both client and server development teams need to communicate directly and -discuss changes while going through the process. CDC is all about communication. +* Fraud Detection has an `artifact-id` of `http-server`, +* Loan +Issuance has an artifact-id of `http-client` +* both have a `group-id` of `com.example` +* For the sake of this example the `Stub Storage` will be Nexus / Artifactory + +Social remarks + +* both client and server development teams need to communicate directly and +discuss changes while going through the process +* CDC is all about communication. The https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-server[server side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-client[the @@ -555,7 +625,8 @@ client code here]. TIP: In this case, the producer owns the contracts. Physically, all the contract are in the producer's repository. -==== Technical note +[[getting-started-cdc-technical-note]] +=== Technical note If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the following section to your build: @@ -572,7 +643,13 @@ include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repo include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0] ---- -==== Consumer side (Loan Issuance) +For simplicity we will use the following acronyms: + +- Loan Issuance - LI - the http client +- Fraud Detection - FD - the http server +- Spring Cloud Contract - SCC + +=== Consumer side (Loan Issuance) As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps: @@ -580,7 +657,7 @@ As a developer of the Loan Issuance service (a consumer of the Fraud Detection s . Write the missing implementation. . Clone the Fraud Detection service repository locally. . Define the contract locally in the repo of Fraud Detection service. -. Add the Spring Cloud Contract Verifier plugin. +. Add the Spring Cloud Contract (SCC) plugin. . Run the integration tests. . File a pull request. . Create an initial implementation. @@ -589,6 +666,36 @@ As a developer of the Loan Issuance service (a consumer of the Fraud Detection s . Deploy your app. . Work online. +Let's start with the Loan Issuance flow: + +[plantuml, getting-started-cdc-client, png] +---- +"Loan\nIssuance"->"Loan\nIssuance": start doing TDD\nby writing a test\nfor your feature +"Loan\nIssuance"->"Loan\nIssuance": write the \nmissing implementation +"Loan\nIssuance"->"Loan\nIssuance": run a test - it fails\ndue to no server running +"Loan\nIssuance"->"Fraud\nDetection\nClone": clone the repository +"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add missing dependencies\n& define contracts +"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add the SCC plugin +"Fraud\nDetection\nClone"->"FD \nClone Build": install the stubs locally +"FD \nClone Build"->"SCC Plugin \nin FD Clone": generate stubs \nand stubs \nartifact (e.g. stubs-jar) +"SCC Plugin \nin FD Clone"->"FD \nClone Build": stubs and artifacts\ngenerated +"FD \nClone Build"->"Local storage": install the stubs locally +"Local storage"->"FD \nClone Build": stub sucessfully installed +"FD \nClone Build"->"Fraud\nDetection\nClone": build successful +"Loan\nIssuance"->"Loan\nIssuance": add a SCC\nStub Runner\ndependency\nand setup +"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nlocal storage +"LI\nSCC\nStub Runner"->"Local storage": find stubs of [FD] +"Local storage"->"LI\nSCC\nStub Runner": stubs of [FD] found +"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] +"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running +"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test +"Loan\nIssuance"->"Loan\nIssuance": run a test +"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub +"FD stub"->"Loan\nIssuance": stub responds successfuly +"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully +"Loan\nIssuance"->"Fraud\nDetection": send a pull request\nwith the\nsuggested contracts +---- + *Start doing TDD by writing a test for your feature.* [source,groovy,indent=0] @@ -656,6 +763,7 @@ of an identifier or a timestamp, you need not hardcode a value. You want to allo different ranges of values. To enable ranges of values, you can set regular expressions matching those values for the consumer side. You can provide the body by means of either a map notation or String with interpolations. +//TODO: Ensure that the links are correct Consult the <> section for more information. We highly recommend using the map notation! TIP: You must understand the map notation in order to set up contracts. Please read the @@ -793,11 +901,37 @@ you wish. Once you are satisfied with the results and the test passes, publish a pull request to the server side. Currently, the consumer side work is done. -==== Producer side (Fraud Detection server) +=== Producer side (Fraud Detection server) -As a developer of the Fraud Detection server (a server to the Loan Issuance service): +As a developer of the Fraud Detection server (a server to the Loan Issuance service) we would like to: -*Create an initial implementation.* +- take over the pull request +- write the missing implementation +- deploy the application + +Let's look at the Fraud Detection flow: + +[plantuml, getting-started-cdc-server, png] +---- +"Fraud\nDetection"->"Fraud\nDetection": take over the\n pull request +"Fraud\nDetection"->"Fraud\nDetection": setup\nSpring Cloud\nContract plugin +"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build +"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) +"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated +"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests +"Fraud\nDetection\nBuild"->"Fraud\nDetection": generated tests failed! +"Fraud\nDetection"->"Fraud\nDetection": setup\nbase classes\nfor contract tests +"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build +"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) +"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated +"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests +"Fraud\nDetection\nBuild"->"Fraud\nDetection": all the tests passed! +"Fraud\nDetection"->"Fraud\nDetection": commit and push changes +"Fraud\nDetection"->"CI": commit pushed!\nTriggers the build +"CI"->"Stub Storage": build successful,\nupload artifacts +---- + +*Take over the pull request.* As a reminder, you can see the initial implementation here: @@ -808,8 +942,6 @@ include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com } ---- -*Take over the pull request.* - [source,bash,indent=0] ---- $ git checkout -b contract-change-pr master @@ -924,9 +1056,29 @@ $ git push origin master Your CI might run something like `./mvnw clean deploy`, which would publish both the application and the stub artifacts. -==== Consumer Side (Loan Issuance) Final Step +=== Consumer Side (Loan Issuance) Final Step -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server): +As a developer of the Loan Issuance service (a consumer of the Fraud Detection server) we will want to: + +- merge our feature branch to `master` +- switch to online mode of working + + +[plantuml, getting-started-cdc-client, png] +---- +"Loan\nIssuance"->"Loan\nIssuance": merge the\nfeature branch\nto master branch +"Loan\nIssuance"->"Loan\nIssuance": setup SCC Stub Runner\nto fetch stubs\nfrom Stub Storage +"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nStub Storage +"LI\nSCC\nStub Runner"->"Stub Storage": find stubs of [FD] +"Stub Storage"->"LI\nSCC\nStub Runner": stubs of [FD] found +"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] +"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running +"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test +"Loan\nIssuance"->"Loan\nIssuance": run a test +"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub +"FD stub"->"Loan\nIssuance": stub responds successfuly +"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully +---- *Merge branch to master.* @@ -946,7 +1098,40 @@ achieving the same thing by changing the properties. [source,yaml,indent=0] ---- -include::{github-raw}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] +include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ---- -That's it! \ No newline at end of file +That's it! + +[[getting-started-whats-next]] +== What to Read Next +Hopefully, this section provided some of the {project-full-name} basics and got you on your way +to writing your own applications. If you are a task-oriented type of developer, you might +want to jump over to https://spring.io and check out some of the +https://spring.io/guides/[getting started] guides that solve specific "`How do I do that +with Spring?`" problems. We also have {project-full-name}-specific +"`<>`" reference documentation. + +Otherwise, the next logical step is to read _<>_. If +you are really impatient, you could also jump ahead and read about +_<>_. + +// TODO: Check if we can link courses +In addition to that you can check out the following videos: + +- "Consumer Driven Contracts and Your Microservice Architecture" by Olga Maciaszek-Sharma and Marcin Grzejszczak + +video::pDkC_00hhvA[youtube,width=640,height=480] + +- "Contract Tests in the Enterprise" by Marcin Grzejszczak + +video::ZyHG-VOzPZg[youtube,width=640,height=480] + +- "Why Contract Tests Matter?" by Marcin Grzejszczak + +video::TvpkZu1e2Dc[youtube,start=6262,width=640,height=480] + +You can find the default project samples at +https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. + +You can find the Spring Cloud Contract workshops https://cloud-samples.spring.io/spring-cloud-contract-samples/[over here]. \ No newline at end of file diff --git a/docs/src/main/asciidoc/_links.adoc b/docs/src/main/asciidoc/_links.adoc deleted file mode 100644 index e6104db048..0000000000 --- a/docs/src/main/asciidoc/_links.adoc +++ /dev/null @@ -1,11 +0,0 @@ -== Links - -The following links may be helpful when working with Spring Cloud Contract: - -* https://github.com/spring-cloud/spring-cloud-contract/[Spring Cloud Contract Github -Repository] -* https://github.com/spring-cloud-samples/spring-cloud-contract-samples/[Spring Cloud -Contract Samples] -* https://gitter.im/spring-cloud/spring-cloud-contract[Spring Cloud Contract Gitter] -* https://www.youtube.com/watch?v=sAAklvxmPmk[Spring Cloud Contract WJUG Presentation by -Marcin Grzejszczak] diff --git a/docs/src/main/asciidoc/_verifier_contract.adoc b/docs/src/main/asciidoc/_project-features-contract.adoc similarity index 60% rename from docs/src/main/asciidoc/_verifier_contract.adoc rename to docs/src/main/asciidoc/_project-features-contract.adoc index 2ca22db220..df80880e4a 100644 --- a/docs/src/main/asciidoc/_verifier_contract.adoc +++ b/docs/src/main/asciidoc/_project-features-contract.adoc @@ -1,8 +1,12 @@ [[contract-dsl]] == Contract DSL +include::_attributes.adoc[] -Spring Cloud Contract supports out of the box 2 types of DSL. One written in -`Groovy` and one written in `YAML`. +Spring Cloud Contract supports out of the box following types of DSL. + +* written in `Groovy` +* written in `YAML` +* written in `Java` If you decide to write the contract in Groovy, do not be alarmed if you have not used Groovy before. Knowledge of the language is not really needed, as the Contract DSL uses only a @@ -17,23 +21,26 @@ the `Contract` class: `import org.springframework.cloud.spec.Contract` and then TIP: Spring Cloud Contract supports defining multiple contracts in a single file. -The following is a complete example of a Groovy contract definition: +The following is a complete example of a contract definition: -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy[tags=dsl_example,indent=0] +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[tags=dsl_example,indent=0] ---- -The following is a complete example of a YAML contract definition: - -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0] ---- +==== TIP: You can compile contracts to stubs mapping using standalone maven command: `mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert` +[[contract-limitations]] === Limitations WARNING: Spring Cloud Contract Verifier does not properly support XML. Please use JSON or @@ -48,6 +55,7 @@ WARNING: Because JSON structure can have any form, it can be impossible to parse properly when using the Groovy DSL and the `value(consumer(...), producer(...))` notation in `GString`. That is why you should use the Groovy Map notation. +[[contract-common-top-elements]] === Common Top-Level elements The following sections describe the most common top-level elements: @@ -72,7 +80,7 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0] ---- @@ -99,7 +107,7 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=name,indent=0] ---- @@ -119,7 +127,7 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=ignored,indent=0] ---- @@ -152,7 +160,7 @@ include::{verifier_core_path}/src/test/resources/classpath/readFromFile.groovy[i ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_from_file.yml[indent=0] ---- @@ -160,17 +168,19 @@ include::{verifier_core_path}/src/test/resources/yml/contract_from_file.yml[inde Further assume that the JSON files is as follows: -*request.json* -[source,json,indent=0] +==== +[source,json,indent=0,subs="verbatim,attributes",role="primary"] +.request.json ---- include::{verifier_core_path}/src/test/resources/classpath/request.json[indent=0] ---- -*response.json* -[source,json,indent=0] +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.response.json ---- include::{verifier_core_path}/src/test/resources/classpath/response.json[indent=0] ---- +==== When test or stub generation takes place, the contents of the file is passed to the body of a request or a response. The name of the file needs to be a file with location @@ -187,7 +197,7 @@ include::{verifier_core_path}/src/test/resources/body_builder/worksWithPdf.groov ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_pdf.yml[indent=0] ---- @@ -201,14 +211,15 @@ IMPORTANT: You should use this approach whenever you want to work with binary pa The following methods can be called in the top-level closure of a contract definition. `request` and `response` are mandatory. `priority` is optional. -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=http_dsl,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=priority,indent=0] include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0] @@ -216,12 +227,14 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,indent=0] ... ---- +==== IMPORTANT: If you want to make your contract have a **higher** value of priority you need to pass a **lower** number to the `priority` tag / method. E.g. `priority` with value `5` has **higher** priority than `priority` with value `10`. -=== Request +[[contract-dsl-request]] +=== HTTP Request The HTTP protocol requires only **method and url** to be specified in a request. The same information is mandatory in request definition of the Contract. @@ -234,7 +247,7 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request_obligatory,indent=0] ---- @@ -251,7 +264,7 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_rest_with_path.yml[tags=url_path,indent=0] ---- @@ -259,67 +272,75 @@ include::{verifier_core_path}/src/test/resources/yml/contract_rest_with_path.yml `request` may contain **query parameters**. -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=urlpath,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0] ... include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=query_params,indent=0] ---- +==== `request` may contain additional **request headers**, as shown in the following example: -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=headers,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0] ... include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=headers,indent=0] ---- +==== `request` may contain additional **request cookies**, as shown in the following example: -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=cookies,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0] ... include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=cookies,indent=0] ---- +==== `request` may contain a **request body**: -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=body,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0] ... include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=body,indent=0] ---- +==== `request` may contain **multipart** elements. To include multipart elements, use the `multipart` method/section, as shown in the following examples @@ -328,11 +349,11 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=body,inde [source,groovy,indent=0,role="primary"] .groovy ---- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy[tags=multipartdsl,indent=0] +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[tags=multipartdsl,indent=0] ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_multipart.yml[indent=0] ---- @@ -362,11 +383,13 @@ define the parameter name via `paramName` and then you can pass the parametrization of either `fileName` or `fileContent` via `regex` or a `predefined` regular expression -From this contract, the generated test is as follows: +From this contract, the generated test and stubs look as follows: -[source,java,indent=0] +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.Test ---- - // given: +// given: MockMvcRequestSpecification request = given() .header("Content-Type", "multipart/form-data;boundary=AaB03x") .param("formParameter", "\"formParameterValue\"") @@ -381,31 +404,34 @@ From this contract, the generated test is as follows: assertThat(response.statusCode()).isEqualTo(200); ---- -The WireMock stub is as follows: - -[source,json,indent=0] +[source,json,indent=0,subs="verbatim,attributes",role="secondary"] +.Stub ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy[tags=multipartwiremock,indent=0] ---- +==== -=== Response +[[contract-dsl-response]] +=== HTTP Response The response must contain an **HTTP status code** and may contain other information. The following code shows an example: -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=response,indent=0] ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,indent=0] ... include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response_obligatory,indent=0] ---- +==== Besides status, the response may contain **headers**, **cookies** and a **body**, both of which are specified the same way as in the request (see the previous paragraph). @@ -414,6 +440,7 @@ TIP: Via the Groovy DSL you can reference the `org.springframework.cloud.contrac methods to provide a meaningful status instead of a digit. E.g. you can call `OK()` for a status `200` or `BAD_REQUEST()` for `400`. +[[contract-dsl-dynamic-properties]] === Dynamic properties The contract can contain some dynamic properties: timestamps, IDs, and so on. You do not @@ -429,16 +456,19 @@ check out the https://github.com/spring-cloud/spring-cloud-contract/wiki/Spring- For YAML you can only use the `matchers` section. +[[contract-dsl-dynamic-properties-in-body]] ==== Dynamic properties inside the body IMPORTANT: This section is valid only for Groovy DSL. Check out the -<> section for YAML examples of a similar feature. +<> section for YAML examples of a similar feature. You can set the properties inside the body either with the `value` method or, if you use the Groovy map notation, with `$()`. The following example shows how to set dynamic properties with the value method: -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.value ---- value(consumer(...), producer(...)) value(c(...), p(...)) @@ -446,23 +476,24 @@ value(stub(...), test(...)) value(client(...), server(...)) ---- -The following example shows how to set dynamic properties with `$()`: - -[source,groovy,indent=0] +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.$ ---- $(consumer(...), producer(...)) $(c(...), p(...)) $(stub(...), test(...)) $(client(...), server(...)) ---- +==== Both approaches work equally well. `stub` and `client` methods are aliases over the `consumer` method. Subsequent sections take a closer look at what you can do with those values. +[[contract-dsl-regex]] ==== Regular expressions IMPORTANT: This section is valid only for Groovy DSL. Check out the -<> section for YAML examples of a similar feature. +<> section for YAML examples of a similar feature. You can use regular expressions to write your requests in Contract DSL. Doing so is particularly useful when you want to indicate that a given response should be provided @@ -472,7 +503,7 @@ need to use patterns and not exact values both for your test and your server sid Make sure that regex matches a whole region of a sequence as internally a call to https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#matches--[Pattern.matches()] is called. For instance, `abc` pattern doesn't match `aabc` string but `.abc` does. -There are several additional <> as well. +There are several additional <> as well. The following example shows how to use regular expressions to write a request: @@ -523,7 +554,7 @@ and this is an example of how you can reference those methods: include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=regex_creating_props,indent=0] ---- -[[regex-limitations]] +[[contract-dsl-regex-limitations]] ===== Limitations WARNING: Due to certain limitations of `Xeger` library that generates string out of @@ -534,10 +565,11 @@ WARNING: Do not use `LocalDate` instance as a value for `$` like this `$(consume It causes `java.lang.StackOverflowError`. Use `$(consumer(LocalDate.now().toString()))` instead. https://github.com/spring-cloud/spring-cloud-contract/issues/900[Issue 900] +[[contract-dsl-optional-params]] ==== Passing Optional Parameters IMPORTANT: This section is valid only for Groovy DSL. Check out the -<> section for YAML examples of a similar feature. +<> section for YAML examples of a similar feature. It is possible to provide optional parameters in your contract. However, you can provide optional parameters only for the following: @@ -569,10 +601,11 @@ The following stub would also be generated: include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=wiremock,indent=0] ---- +[[contract-dsl-custom-methods]] ==== Executing Custom Methods on the Server Side IMPORTANT: This section is valid only for Groovy DSL. Check out the -<> section for YAML examples of a similar feature. +<> section for YAML examples of a similar feature. You can define a method call that executes on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" in the configuration. The @@ -634,6 +667,7 @@ It should resemble the following code: assertThat(response.statusCode()).isEqualTo(200); ---- +[[contract-dsl-referencing-request-from-response]] ==== Referencing the Request from the Response The best situation is to provide fixed values, but sometimes you need to reference a @@ -677,11 +711,11 @@ Consider the following contract: [source,groovy,indent=0,role="primary"] .groovy ---- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy[tags=template_contract,indent=0] +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[tags=template_contract,indent=0] ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_reference_request.yml[indent=0] ---- @@ -786,32 +820,7 @@ proper values. Additionally, it registers two helper functions: * `escapejsonbody`: Escapes the request body in a format that can be embedded in a JSON. * `jsonpath`: For a given parameter, find an object in the request body. -==== Registering Your Own WireMock Extension - -WireMock lets you register custom extensions. By default, Spring Cloud Contract registers -the transformer, which lets you reference a request from a response. If you want to -provide your own extensions, you can register an implementation of the -`org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions` interface. -Since we use the spring.factories extension approach, you can create an entry in -`META-INF/spring.factories` file similar to the following: - -[source,groovy,indent=0] ----- -include::{stubrunner_core_path}/src/test/resources/META-INF/spring.factories[indent=0] ----- - -The following is an example of a custom extension: - -.TestWireMockExtensions.groovy -[source,groovy,indent=0] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/TestWireMockExtensions.groovy[indent=0] ----- - -IMPORTANT: Remember to override the `applyGlobally()` method and set it to `false` if you -want the transformation to be applied only for a mapping that explicitly requires it. - -[[contract-matchers]] +[[contract-dsl-matchers]] ==== Dynamic Properties in the Matchers Sections If you work with https://docs.pact.io/[Pact], the following discussion may seem familiar. @@ -937,7 +946,7 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_matchers.yml[indent=0] ---- @@ -1079,35 +1088,18 @@ As you can see, the assertion is malformed. Only the first element of the array asserted. In order to fix this, you should apply the assertion to the whole `$.events` collection and assert it with the `byCommand(...)` method. -=== JAX-RS Support -The Spring Cloud Contract Verifier supports the JAX-RS 2 Client API. The base class needs -to define `protected WebTarget webTarget` and server initialization. The only option for -testing JAX-RS API is to start a web server. Also, a request with a body needs to have a -content type set. Otherwise, the default of `application/octet-stream` gets used. - -In order to use JAX-RS mode, use the following settings: - -[source,groovy,indent=0] ----- -testMode == 'JAXRSCLIENT' ----- - -The following example shows a generated test API: - -[source,groovy,indent=0] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy[tags=jaxrs,indent=0] ----- +[[contract-dsl-async]] === Async Support If you're using asynchronous communication on the server side (your controllers are returning `Callable`, `DeferredResult`, and so on), then, inside your contract, you must provide an `async()` method in the `response` section. The following code shows an example: -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- org.springframework.cloud.contract.spec.Contract.make { request { @@ -1122,17 +1114,19 @@ org.springframework.cloud.contract.spec.Contract.make { } ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- response: async: true ---- +==== You can also use the `fixedDelayMilliseconds` method / property to add delay to your stubs. -.Groovy DSL -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- org.springframework.cloud.contract.spec.Contract.make { request { @@ -1147,159 +1141,15 @@ org.springframework.cloud.contract.spec.Contract.make { } ---- -.YAML -[source,yml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- response: fixedDelayMilliseconds: 1000 ---- +==== -=== Working with Context Paths - -Spring Cloud Contract supports context paths. - -IMPORTANT: The only change needed to fully support context paths is the switch on the -*PRODUCER* side. Also, the autogenerated tests must use *EXPLICIT* mode. The consumer -side remains untouched. In order for the generated test to pass, you must use *EXPLICIT* -mode. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - - EXPLICIT - - ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -contracts { - testMode = 'EXPLICIT' -} ----- - -That way, you generate a test that *DOES NOT* use MockMvc. It means that you generate -real requests and you need to setup your generated test's base class to work on a real -socket. - -Consider the following contract: - -[source,groovy,indent=0] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_contract,indent=0] ----- - -The following example shows how to set up a base class and Rest Assured: - -[source,groovy,indent=0] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_baseclass,indent=0] ----- - -If you do it this way: - -* All of your requests in the autogenerated tests are sent to the real endpoint with your -context path included (for example, `/my-context-path/url`). -* Your contracts reflect that you have a context path. Your generated stubs also have -that information (for example, in the stubs, you have to call `/my-context-path/url`). - -=== Working with WebFlux - -Spring Cloud Contract offers two ways of working with WebFlux. - -==== WebFlux with WebTestClient - -One of them is via the `WebTestClient` mode. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - - WEBTESTCLIENT - - ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -contracts { - testMode = 'WEBTESTCLIENT' -} ----- - -The following example shows how to set up a `WebTestClient` base class and `RestAssured` -for WebFlux: - -[source,groovy,indent=0] ----- -import io.restassured.module.webtestclient.RestAssuredWebTestClient; -import org.junit.Before; - -public abstract class BeerRestBase { - - @Before - public void setup() { - RestAssuredWebTestClient.standaloneSetup( - new ProducerController(personToCheck -> personToCheck.age >= 20)); - } -} -} ----- - -==== WebFlux with Explicit mode - -Another way is with the `EXPLICIT` mode in your generated tests -to work with WebFlux. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - - EXPLICIT - - ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -contracts { - testMode = 'EXPLICIT' -} ----- - -The following example shows how to set up a base class and Rest Assured for Web Flux: - -[source,groovy,indent=0] ----- -include::{samples_url}/producer_webflux/src/test/java/com/example/BeerRestBase.java[tags=annotations,indent=0] - - // your tests go here - - // in this config class you define all controllers and mocked services -include::{samples_url}/producer_webflux/src/test/java/com/example/BeerRestBase.java[tags=config,indent=0] - -} ----- - +[[contract-dsl-xml]] === XML Support for REST For REST contracts, we also support XML request and response body. The XML body has to be passed within the `body` element @@ -1310,17 +1160,19 @@ and the appropriate `MatchingType` as second. All the body matchers apart from ` Here is an example of a Groovy DSL contract with XML response body: -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/XmlMethodBodyBuilderSpec.groovy[tags=xmlgroovy] ---- -And below is an example of a YAML contract with XML request and response bodies: - -[source,yaml,indent=0] +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml ---- include::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.yml ---- +==== Here is an example of an automatically generated test for XML response body: @@ -1353,87 +1205,7 @@ public void validate_xmlMatches() throws Exception { } ---- -=== Messaging Top-Level Elements - -The DSL for messaging looks a little bit different than the one that focuses on HTTP. The -following sections explain the differences: - -* <> -* <> -* <> -* <> - -[[contract-dsl-output-triggered-method]] -==== Output Triggered by a Method - -The output message can be triggered by calling a method (such as a `Scheduler` when a was -started and a message was sent), as shown in the following example: - -.Groovy DSL -[source,groovy] ----- -include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=method_trigger,indent=0] ----- - -.YAML -[source,yml,indent=0] ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_method.yml[indent=0] ----- - -In the previous example case, the output message is sent to `output` if a method called -`bookReturnedTriggered` is executed. On the message *publisher's* side, we generate a -test that calls that method to trigger the message. On the *consumer* side, you can use -the `some_label` to trigger the message. - -[[contract-dsl-output-triggered-message]] -==== Output Triggered by a Message - -The output message can be triggered by receiving a message, as shown in the following -example: - -.Groovy DSL -[source,groovy] ----- -include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=message_trigger,indent=0] ----- - -.YAML -[source,yml,indent=0] ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_input_message.yml[indent=0] ----- - -In the preceding example, the output message is sent to `output` if a proper message is -received on the `input` destination. On the message *publisher's* side, the engine -generates a test that sends the input message to the defined destination. On the -*consumer* side, you can either send a message to the input destination or use a label -(`some_label` in the example) to trigger the message. - -[[contract-dsl-consumer-producer]] -==== Consumer/Producer - -IMPORTANT: This section is valid only for Groovy DSL. - -In HTTP, you have a notion of `client`/`stub and `server`/`test` notation. You can also -use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also -provides the `consumer` and `producer` methods, as presented in the following example -(note that you can use either `$` or `value` methods to provide `consumer` and `producer` -parts): - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=consumer_producer] ----- - -[[contract-dsl-common]] -==== Common - -In the `input` or `outputMessage` section you can call `assertThat` with the name -of a `method` (e.g. `assertThatMessageIsOnTheQueue()`) that you have defined in the -base class or in a static import. Spring Cloud Contract will execute that method -in the generated test. - +[[contract-dsl-multiple]] === Multiple Contracts in One File You can define multiple contracts in one file. Such a contract might resemble the @@ -1447,7 +1219,7 @@ include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/mul ---- [source,yaml,indent=0,role="secondary"] -.yaml +.yml ---- include::{verifier_core_path}/src/test/resources/yml/multiple_contracts.yml[indent=0] ---- @@ -1522,565 +1294,37 @@ case, the contract had an index of `1` in the list of contracts in the file). TIP: As you can see, it is much better if you name your contracts because doing so makes your tests far more meaningful. -=== Generating Spring REST Docs snippets from the contracts +[[contract-stateful-contracts]] +== Stateful Contracts -When you want to include the requests and responses of your API using Spring REST Docs, -you only need to make some minor changes to your setup if you are using MockMvc and RestAssuredMockMvc. -Simply include the following dependencies if you haven't already. +Stateful contracts (known also as scenarios) are contract definitions that should be read in order. This might be useful in the following situations: -[source,xml,indent=0] -.Maven +* you want to execute the contract in a precisely defined order, since you're using Spring Cloud Contract to test your stateful application +** NOTE: we really discourage you to do that since contract tests should be stateless +* you want the same endpoint to return different results for the same request + +All you need to do is to +stick to the proper naming convention while creating your contracts. The convention +requires including an order number followed by an underscore. This will work regardless + of whether you're working with YAML or Groovy. Example: + +[source,indent=0] ---- -include::{standalone_restdocs_path}/http-server/pom.xml[tags=dependencies,indent=0] +my_contracts_dir\ + scenario1\ + 1_login.groovy + 2_showCart.groovy + 3_logout.groovy ---- -[source,groovy,indent=0] -.Gradle ----- -include::{standalone_restdocs_path}/http-server/build.gradle[tags=dependencies,indent=0] ----- +Such a tree causes Spring Cloud Contract Verifier to generate WireMock's scenario with a +name of `scenario1` and the three following steps: -Next you need to make some changes to your base class like the following example. +. login marked as `Started` pointing to... +. showCart marked as `Step1` pointing to... +. logout marked as `Step2` which will close the scenario. -[source,java,indent=0] ----- -include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java[tags=base_class,indent=0] ----- +More details about WireMock scenarios can be found at +https://wiremock.org/docs/stateful-behaviour/[https://wiremock.org/docs/stateful-behaviour/] -In case you are using the standalone setup, you can set up RestAssuredMockMvc like this: - -[source,java,indent=0] ----- -include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java[tags=base_class,indent=0] ----- - -TIP: You don't need to specify the output directory for the generated snippets since version 1.2.0.RELEASE of Spring REST Docs. - -=== DSL Customization - -IMPORTANT: This section is valid only for Groovy DSL - -You can customize the Spring Cloud Contract Verifier by extending the DSL, as shown in -the remainder of this section. - -==== Extending the DSL - -You can provide your own functions to the DSL. The key requirement for this feature is to -maintain the static compatibility. Later in this document, you can see examples of: - -* Creating a JAR with reusable classes. -* Referencing of these classes in the DSLs. - -You can find the full example -https://github.com/spring-cloud-samples/spring-cloud-contract-samples[here]. - -===== Common JAR - -The following examples show three classes that can be reused in the DSLs. - -*PatternUtils* contains functions used by both the **consumer** and the **producer**. - -[source,java] ----- -include::{samples_url}/common/src/main/java/com/example/PatternUtils.java[] ----- - -*ConsumerUtils* contains functions used by the **consumer**. - -[source,java] ----- -include::{samples_url}/common/src/main/java/com/example/ConsumerUtils.java[] ----- - -*ProducerUtils* contains functions used by the **producer**. - -[source,java] ----- -include::{samples_url}/common/src/main/java/com/example/ProducerUtils.java[] ----- - -===== Adding the Dependency to the Project - -In order for the plugins and IDE to be able to reference the common JAR classes, you need -to pass the dependency to your project. - -// TODO missing code block here - we should show an example adding a dependency - -===== Test the Dependency in the Project's Dependencies - -First, add the common jar dependency as a test dependency. Because your contracts files -are available on the test resources path, the common jar classes automatically become -visible in your Groovy files. The following examples show how to test the dependency: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{samples_url}/producer/pom.xml[tags=test_dep,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{samples_url}/producer/build.gradle[tags=test_dep,indent=0] ----- - -===== Test a Dependency in the Plugin's Dependencies - -Now, you must add the dependency for the plugin to reuse at runtime, as shown in the -following example: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{samples_url}/producer/pom.xml[tags=test_dep_in_plugin,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{samples_url}/producer/build.gradle[tags=test_dep_in_plugin,indent=0] ----- - -===== Referencing classes in DSLs - -You can now reference your classes in your DSL, as shown in the following example: - -[source,groovy] ----- -include::{samples_url}/producer/src/test/resources/contracts/beer/rest/shouldGrantABeerIfOldEnough.groovy[indent=0] ----- - -IMPORTANT: You can set the Spring Cloud Contract plugin up by setting `convertToYaml` to `true`. That way you will NOT have to add the dependency with the extended functionality to the consumer side, since the consumer side will be using YAML contracts instead of Groovy ones. - -== Using the Pluggable Architecture - -You may encounter cases where you have your contracts have been defined in other formats, -such as YAML, RAML or PACT. In those cases, you still want to benefit from the automatic -generation of tests and stubs. You can add your own implementation for generating both -tests and stubs. Also, you can customize the way tests are generated (for example, you -can generate tests for other languages) and the way stubs are generated (for example, you -can generate stubs for other HTTP server implementations). - -=== Custom Contract Converter - -The `ContractConverter` interface lets you register your own implementation of a contract -structure converter. The following code listing shows the `ContractConverter` interface: - -[source,java] ----- -include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java[indent=0,lines=17..-1] ----- - -Your implementation must define the condition on which it should start the -conversion. Also, you must define how to perform that conversion in both directions. - -IMPORTANT: Once you create your implementation, you must create a -`/META-INF/spring.factories` file in which you provide the fully qualified name of your -implementation. - -The following example shows a typical `spring.factories` file: - -[source] ----- -org.springframework.cloud.contract.spec.ContractConverter=\ -org.springframework.cloud.contract.verifier.converter.YamlContractConverter ----- - -[[pact-converter]] -==== Pact Converter - -Spring Cloud Contract includes support for https://docs.pact.io/[Pact] representation of -contracts up until v4. Instead of using the Groovy DSL, you can use Pact files. In this section, we -present how to add Pact support for your project. Note however that not all functionality is supported. -Starting with v3 you can combine multiple matcher for the same element; -you can use matchers for the body, headers, request and path; and you can use value generators. -Spring Cloud Contract currently only supports multiple matchers that are combined using the AND rule logic. -Next to that the request and path matchers are skipped during the conversion. -When using a date, time or datetime value generator with a given format, -the given format will be skipped and the ISO format will be used. - -In order to properly support the Spring Cloud Contract way of doing messaging -with Pact you'll have to provide some additional meta data entries. Below you can find a list of such entries: - -- to define the destination to which a message gets sent, you have to -set a `metaData` entry in the Pact file, with key `sentTo` equal to the destination to which a message is to be sent. E.g. `"metaData": { "sentTo": "activemq:output" }` - -==== Pact Contract - -Consider following example of a Pact contract, which is a file under the -`src/test/resources/contracts` folder. - -[source,javascript,indent=0] ----- -include::{standalone_pact_path}/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json[indent=0] ----- - -The remainder of this section about using Pact refers to the preceding file. - -==== Pact for Producers - -On the producer side, you must add two additional dependencies to your plugin -configuration. One is the Spring Cloud Contract Pact support, and the other represents -the current Pact version that you use. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{standalone_pact_path}/pact-http-server/pom.xml[tags=pact_dependency,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{standalone_pact_path}/pact-http-server/build.gradle[tags=pact_dependency,indent=0] ----- - -When you execute the build of your application, a test will be generated. The generated -test might be as follows: - -[source,java,indent=0] ----- - @Test - public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd\\.fraud\\.v1\\+json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['rejectionReason']").isEqualTo("Amount too high"); - // and: - assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD"); - } ----- - -The corresponding generated stub might be as follows: - -[source,javascript,indent=0] ----- -{ - "id" : "996ae5ae-6834-4db6-8fac-358ca187ab62", - "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62", - "request" : { - "url" : "/fraudcheck", - "method" : "PUT", - "headers" : { - "Content-Type" : { - "matches" : "application/vnd\\.fraud\\.v1\\+json.*" - } - }, - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.['loanAmount'] == 99999)]" - }, { - "matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]" - } ] - }, - "response" : { - "status" : 200, - "body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}", - "headers" : { - "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8" - }, - "transformers" : [ "response-template" ] - }, -} ----- - -==== Pact for Consumers - -On the producer side, you must add two additional dependencies to your project -dependencies. One is the Spring Cloud Contract Pact support, and the other represents the -current Pact version that you use. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{standalone_pact_path}/pact-http-client/pom.xml[tags=pact_dependency,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{standalone_pact_path}/pact-http-client/build.gradle[tags=pact_dependency,indent=0] ----- - -=== Using the Custom Test Generator - -If you want to generate tests for languages other than Java or you are not happy with the -way the verifier builds Java tests, you can register your own implementation. - -The `SingleTestGenerator` interface lets you register your own implementation. The -following code listing shows the `SingleTestGenerator` interface: - -[source,groovy] ----- -include::{verifier_core_path}/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java[indent=0,lines=17..-1] ----- - -Again, you must provide a `spring.factories` file, such as the one shown in the following -example: - -[source] ----- -org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/ -com.example.MyGenerator ----- - -=== Using the Custom Stub Generator - -If you want to generate stubs for stub servers other than WireMock, you can plug in your -own implementation of the `StubGenerator` interface. The following code listing shows the -`StubGenerator` interface: - -[source,groovy] ----- -include::{converters_path}/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGenerator.groovy[indent=0,lines=16..-1] ----- - -Again, you must provide a `spring.factories` file, such as the one shown in the following -example: - -[source] ----- -include::{converters_path}/src/main/resources/META-INF/spring.factories[indent=0] ----- - -The default implementation is the WireMock stub generation. - -TIP: You can provide multiple stub generator implementations. For example, from a single -DSL, you can produce both WireMock stubs and Pact files. - -=== Using the Custom Stub Runner - -If you decide to use a custom stub generation, you also need a custom way of running -stubs with your different stub provider. - -Assume that you use https://github.com/dreamhead/moco[Moco] to build your stubs and that -you have written a stub generator and placed your stubs in a JAR file. - -In order for Stub Runner to know how to run your stubs, you have to define a custom -HTTP Stub server implementation, which might resemble the following example: - -[source,groovy] ----- -include::{tests_path}/spring-cloud-contract-stub-runner-moco/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/moco/MocoHttpServerStub.groovy[indent=0,lines=16..-1] ----- - -Then, you can register it in your `spring.factories` file, as shown in the following -example: - -[source] ----- -org.springframework.cloud.contract.stubrunner.HttpServerStub=\ -org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub ----- - -Now you can run stubs with Moco. - -IMPORTANT: If you do not provide any implementation, then the default (WireMock) -implementation is used. If you provide more than one, the first one on the list is used. - -=== Using the Custom Stub Downloader - -You can customize the way your stubs are downloaded by creating an implementation of the -`StubDownloaderBuilder` interface, as shown in the following example: - -[source,java] ----- -package com.example; - -class CustomStubDownloaderBuilder implements StubDownloaderBuilder { - - @Override - public StubDownloader build(final StubRunnerOptions stubRunnerOptions) { - return new StubDownloader() { - @Override - public Map.Entry downloadAndUnpackStubJar( - StubConfiguration config) { - File unpackedStubs = retrieveStubs(); - return new AbstractMap.SimpleEntry<>( - new StubConfiguration(config.getGroupId(), config.getArtifactId(), version, - config.getClassifier()), unpackedStubs); - } - - File retrieveStubs() { - // here goes your custom logic to provide a folder where all the stubs reside - } -} ----- - -Then you can register it in your `spring.factories` file, as shown in the following -example: - -[source] ----- -# Example of a custom Stub Downloader Provider -org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\ -com.example.CustomStubDownloaderBuilder ----- - -Now you can pick a folder with the source of your stubs. - -IMPORTANT: If you do not provide any implementation, then the default is used (scan classpath). -If you provide the `stubsMode = StubRunnerProperties.StubsMode.LOCAL` or -`, stubsMode = StubRunnerProperties.StubsMode.REMOTE` then the Aether implementation will be used -If you provide more than one, then the first one on the list is used. - -[[scm-stub-downloader]] -=== Using the SCM Stub Downloader - -Whenever the `repositoryRoot` starts with a SCM protocol -(currently we support only `git://`), the stub downloader will try -to clone the repository and use it as a source of contracts -to generate tests or stubs. - -Either via environment variables, system properties, properties set -inside the plugin or contracts repository configuration you can -tweak the downloader's behaviour. Below you can find the list of -properties - -.SCM Stub Downloader properties -|=== -|Type of a property |Name of the property | Description -| -* `git.branch` (plugin prop) - -* `stubrunner.properties.git.branch` (system prop) - -* `STUBRUNNER_PROPERTIES_GIT_BRANCH` (env prop) -|master -|Which branch to checkout - -| -* `git.username` (plugin prop) - -* `stubrunner.properties.git.username` (system prop) - -* `STUBRUNNER_PROPERTIES_GIT_USERNAME` (env prop) -| -|Git clone username - -| -* `git.password` (plugin prop) - -* `stubrunner.properties.git.password` (system prop) - -* `STUBRUNNER_PROPERTIES_GIT_PASSWORD` (env prop) -| -|Git clone password - -| -* `git.no-of-attempts` (plugin prop) - -* `stubrunner.properties.git.no-of-attempts` (system prop) - -* `STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS` (env prop) -|10 -|Number of attempts to push the commits to `origin` - -| -* `git.wait-between-attempts` (Plugin prop) - -* `stubrunner.properties.git.wait-between-attempts` (system prop) - -* `STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS` (env prop) -|1000 -|Number of millis to wait between attempts to push the commits to `origin` -|=== - -[[pact-stub-downloader]] -=== Using the Pact Stub Downloader - -Whenever the `repositoryRoot` starts with a Pact protocol -(starts with `pact://`), the stub downloader will try -to fetch the Pact contract definitions from the Pact Broker. -Whatever is set after `pact://` will be parsed as the Pact Broker URL. - -Either via environment variables, system properties, properties set -inside the plugin or contracts repository configuration you can -tweak the downloader's behaviour. Below you can find the list of -properties - -.SCM Stub Downloader properties -|=== -|Name of a property |Default | Description -| -* `pactbroker.host` (plugin prop) - -* `stubrunner.properties.pactbroker.host` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_HOST` (env prop) -|Host from URL passed to `repositoryRoot` -|What is the URL of Pact Broker - -| -* `pactbroker.port` (plugin prop) - -* `stubrunner.properties.pactbroker.port` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_PORT` (env prop) -|Port from URL passed to `repositoryRoot` -|What is the port of Pact Broker - -| -* `pactbroker.protocol` (plugin prop) - -* `stubrunner.properties.pactbroker.protocol` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL` (env prop) -|Protocol from URL passed to `repositoryRoot` -|What is the protocol of Pact Broker - -| -* `pactbroker.tags` (plugin prop) - -* `stubrunner.properties.pactbroker.tags` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_TAGS` (env prop) -|Version of the stub, or `latest` if version is `+` -|What tags should be used to fetch the stub - -| -* `pactbroker.auth.scheme` (plugin prop) - -* `stubrunner.properties.pactbroker.auth.scheme` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME` (env prop) -|`Basic` -|What kind of authentication should be used to connect to the Pact Broker - -| -* `pactbroker.auth.username` (plugin prop) - -* `stubrunner.properties.pactbroker.auth.username` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME` (env prop) -|The username passed to `contractsRepositoryUsername` (maven) or `contractRepository.username` (gradle) -|Username used to connect to the Pact Broker - -| -* `pactbroker.auth.password` (plugin prop) - -* `stubrunner.properties.pactbroker.auth.password` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD` (env prop) -|The password passed to `contractsRepositoryPassword` (maven) or `contractRepository.password` (gradle) -|Password used to connect to the Pact Broker - -| -* `pactbroker.provider-name-with-group-id` (plugin prop) - -* `stubrunner.properties.pactbroker.provider-name-with-group-id` (system prop) - -* `STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID` (env prop) -|false -|When `true`, the provider name will be a combination of `groupId:artifactId`. If `false`, just `artifactId` is used -|=== +Spring Cloud Contract also generates tests with a guaranteed order of execution. \ No newline at end of file diff --git a/docs/src/main/asciidoc/_project-features-flows.adoc b/docs/src/main/asciidoc/_project-features-flows.adoc new file mode 100644 index 0000000000..7ef5b542ea --- /dev/null +++ b/docs/src/main/asciidoc/_project-features-flows.adoc @@ -0,0 +1,417 @@ +[[features-jax-rs]] +== JAX-RS +include::_attributes.adoc[] + +The Spring Cloud Contract supports the JAX-RS 2 Client API. The base class needs +to define `protected WebTarget webTarget` and server initialization. The only option for +testing JAX-RS API is to start a web server. Also, a request with a body needs to have a +content type set. Otherwise, the default of `application/octet-stream` gets used. + +In order to use JAX-RS mode, use the following settings: + +[source,groovy,indent=0] +---- +testMode == 'JAXRSCLIENT' +---- + +The following example shows a generated test API: + +[source,groovy,indent=0] +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy[tags=jaxrs,indent=0] +---- + +[[contract-webflux]] +== WebFlux + +Spring Cloud Contract offers two ways of working with WebFlux. + +[[contract-webtestclient]] +=== WebFlux with WebTestClient + +One of them is via the `WebTestClient` mode. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + WEBTESTCLIENT + + +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +contracts { + testMode = 'WEBTESTCLIENT' +} +---- +==== + +The following example shows how to set up a `WebTestClient` base class and `RestAssured` +for WebFlux: + +[source,groovy,indent=0] +---- +import io.restassured.module.webtestclient.RestAssuredWebTestClient; +import org.junit.Before; + +public abstract class BeerRestBase { + + @Before + public void setup() { + RestAssuredWebTestClient.standaloneSetup( + new ProducerController(personToCheck -> personToCheck.age >= 20)); + } +} +} +---- + +[[contract-webflux-explicit]] +=== WebFlux with Explicit mode + +Another way is with the `EXPLICIT` mode in your generated tests +to work with WebFlux. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + EXPLICIT + + +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +contracts { + testMode = 'EXPLICIT' +} +---- +==== + +The following example shows how to set up a base class and Rest Assured for Web Flux: + +[source,groovy,indent=0] +---- +include::{samples_url}/producer_webflux/src/test/java/com/example/BeerRestBase.java[tags=annotations,indent=0] + + // your tests go here + + // in this config class you define all controllers and mocked services +include::{samples_url}/producer_webflux/src/test/java/com/example/BeerRestBase.java[tags=config,indent=0] + +} +---- + +[[features-context-paths]] +=== Working with Context Paths + +Spring Cloud Contract supports context paths. + +IMPORTANT: The only change needed to fully support context paths is the switch on the +*PRODUCER* side. Also, the autogenerated tests must use *EXPLICIT* mode. The consumer +side remains untouched. In order for the generated test to pass, you must use *EXPLICIT* +mode. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + EXPLICIT + + +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +contracts { + testMode = 'EXPLICIT' +} +---- +==== + +That way, you generate a test that *DOES NOT* use MockMvc. It means that you generate +real requests and you need to setup your generated test's base class to work on a real +socket. + +Consider the following contract: + +[source,groovy,indent=0] +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_contract,indent=0] +---- + +The following example shows how to set up a base class and Rest Assured: + +[source,groovy,indent=0] +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_baseclass,indent=0] +---- + +If you do it this way: + +* All of your requests in the autogenerated tests are sent to the real endpoint with your +context path included (for example, `/my-context-path/url`). +* Your contracts reflect that you have a context path. Your generated stubs also have +that information (for example, in the stubs, you have to call `/my-context-path/url`). + +[[features-rest-docs]] +=== Working with REST Docs + +https://projects.spring.io/spring-restdocs[Spring REST Docs] can be used to generate +documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc +or `WebTestClient` or Rest Assured. At the same time that you generate documentation for your API, you can also +generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your +normal REST Docs test cases and use `@AutoConfigureRestDocs` to have stubs be +automatically generated in the REST Docs output directory. + +[plantuml, rest-docs, png] +---- +"API Producer"->"API Producer": add Spring Cloud Contract (SCC) \nStub Runner dependency +"API Producer"->"API Producer": setup stub jar assembly +"API Producer"->"API Producer": write and setup REST Docs tests +"API Producer"->"Build": run build +"Build"->"REST Docs": generate API \ndocumentation +"REST Docs"->"SCC": generate stubs from the \nREST Docs tests +"REST Docs"->"SCC": generate contracts from the \nREST Docs tests +"Build"->"Build": assemble stubs jar with \nstubs and contracts +"Build"->"Nexus / Artifactory": upload contracts \nand stubs and the project arifact +"Build"->"API Producer": Build successful +"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency +"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test +"SCC Stub Runner"->"Nexus / Artifactory": test asks for [API Producer] stubs +"Nexus / Artifactory"->"SCC Stub Runner": fetch the [API Producer] stubs +"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs +"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub +"SCC Stub Runner"->"API Consumer": communication is correct +---- + +The following code shows an +example using `MockMvc`: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureMockMvc +public class ApplicationTests { + + @Autowired + private MockMvc mockMvc; + + @Test + public void contextLoads() throws Exception { + mockMvc.perform(get("/resource")) + .andExpect(content().string("Hello World")) + .andDo(document("resource")); + } +} +---- + +This test generates a WireMock stub at "target/snippets/stubs/resource.json". It matches +all GET requests to the "/resource" path. The same example with `WebTestClient` (used +for testing Spring WebFlux applications) would look like this: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureWebTestClient +public class ApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void contextLoads() throws Exception { + client.get().uri("/resource").exchange() + .expectBody(String.class).isEqualTo("Hello World") + .consumeWith(document("resource")); + } +} +---- + +Without any additional configuration, these tests create a stub with a request matcher +for the HTTP method and all headers except "host" and "content-length". To match the +request more precisely (for example, to match the body of a POST or PUT), we need to +explicitly create a request matcher. Doing so has two effects: + +* Creating a stub that matches only in the way you specify. +* Asserting that the request in the test case also matches the same conditions. + +The main entry point for this feature is `WireMockRestDocs.verify()`, which can be used +as a substitute for the `document()` convenience method, as shown in the following +example: + +[source,java,indent=0] + +import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify; + +---- +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureMockMvc +public class ApplicationTests { + + @Autowired + private MockMvc mockMvc; + + @Test + public void contextLoads() throws Exception { + mockMvc.perform(post("/resource") + .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) + .andExpect(status().isOk()) + .andDo(verify().jsonPath("$.id") + .stub("resource")); + } +} +---- + +This contract specifies that any valid POST with an "id" field receives the response +defined in this test. You can chain together calls to `.jsonPath()` to add additional +matchers. If JSON Path is unfamiliar, The https://github.com/jayway/JsonPath[JayWay +documentation] can help you get up to speed. The `WebTestClient` version of this test +has a similar `verify()` static helper that you insert in the same place. + +Instead of the `jsonPath` and `contentType` convenience methods, you can also use the +WireMock APIs to verify that the request matches the created stub, as shown in the +following example: + +[source,java,indent=0] +---- + @Test + public void contextLoads() throws Exception { + mockMvc.perform(post("/resource") + .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) + .andExpect(status().isOk()) + .andDo(verify() + .wiremock(WireMock.post( + urlPathEquals("/resource")) + .withRequestBody(matchingJsonPath("$.id")) + .stub("post-resource")); + } +---- + +The WireMock API is rich. You can match headers, query parameters, and request body by +regex as well as by JSON path. These features can be used to create stubs with a wider +range of parameters. The above example generates a stub resembling the following example: + +.post-resource.json +[source,json] +---- +{ + "request" : { + "url" : "/resource", + "method" : "POST", + "bodyPatterns" : [ { + "matchesJsonPath" : "$.id" + }] + }, + "response" : { + "status" : 200, + "body" : "Hello World", + "headers" : { + "X-Application-Context" : "application:-1", + "Content-Type" : "text/plain" + } + } +} +---- + +NOTE: You can use either the `wiremock()` method or the `jsonPath()` and `contentType()` +methods to create request matchers, but you can't use both approaches. + +On the consumer side, you can make the `resource.json` generated earlier in this section +available on the classpath (by +<= 20)]', byType()) + } + } +} +---- + +The generated document (formatted in Asciidoc in this case) contains a formatted +contract. The location of this file would be `index/dsl-contract.adoc`. diff --git a/docs/src/main/asciidoc/_project-features-messaging.adoc b/docs/src/main/asciidoc/_project-features-messaging.adoc new file mode 100644 index 0000000000..e43743bb16 --- /dev/null +++ b/docs/src/main/asciidoc/_project-features-messaging.adoc @@ -0,0 +1,922 @@ +include::_attributes.adoc[] + +[[features-messaging]] +== Messaging +Spring Cloud Contract lets you verify applications that use messaging as a +means of communication. All of the integrations shown in this document work with Spring, +but you can also create one of your own and use that. + +[[contract-dsl-messaging-top-level]] +=== Messaging DSL Top-Level Elements + +The DSL for messaging looks a little bit different than the one that focuses on HTTP. The +following sections explain the differences: + +* <> +* <> +* <> +* <> + +[[contract-dsl-output-triggered-method]] +==== Output Triggered by a Method + +The output message can be triggered by calling a method (such as a `Scheduler` when a was +started and a message was sent), as shown in the following example: + +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy +---- +include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=method_trigger,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.yml +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_method.yml[indent=0] +---- +==== + +In the previous example case, the output message is sent to `output` if a method called +`bookReturnedTriggered` is executed. On the message *publisher's* side, we generate a +test that calls that method to trigger the message. On the *consumer* side, you can use +the `some_label` to trigger the message. + +[[contract-dsl-output-triggered-message]] +==== Output Triggered by a Message + +The output message can be triggered by receiving a message, as shown in the following +example: + +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy +---- +include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=message_trigger,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.yml +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_input_message.yml[indent=0] +---- +==== + +In the preceding example, the output message is sent to `output` if a proper message is +received on the `input` destination. On the message *publisher's* side, the engine +generates a test that sends the input message to the defined destination. On the +*consumer* side, you can either send a message to the input destination or use a label +(`some_label` in the example) to trigger the message. + +[[contract-dsl-consumer-producer]] +==== Consumer/Producer + +IMPORTANT: This section is valid only for Groovy DSL. + +In HTTP, you have a notion of `client`/`stub and `server`/`test` notation. You can also +use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also +provides the `consumer` and `producer` methods, as presented in the following example +(note that you can use either `$` or `value` methods to provide `consumer` and `producer` +parts): + +[source,groovy] +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=consumer_producer] +---- + +[[contract-dsl-messaging-common]] +==== Common + +In the `input` or `outputMessage` section you can call `assertThat` with the name +of a `method` (e.g. `assertThatMessageIsOnTheQueue()`) that you have defined in the +base class or in a static import. Spring Cloud Contract will execute that method +in the generated test. + +[[features-messaging-integrations]] +=== Integrations + +You can use one of the following four integration configurations: + +* Apache Camel +* Spring Integration +* Spring Cloud Stream +* Spring AMQP + +Since we use Spring Boot, if you have added one of these libraries to the classpath, all +the messaging configuration is automatically set up. + +IMPORTANT: Remember to put `@AutoConfigureMessageVerifier` on the base class of your +generated tests. Otherwise, messaging part of Spring Cloud Contract does not +work. + +IMPORTANT: If you want to use Spring Cloud Stream, remember to add a dependency on +`org.springframework.cloud:spring-cloud-stream-test-support`, as shown below + +===== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + + org.springframework.cloud + spring-cloud-stream-test-support + test + +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +testCompile "org.springframework.cloud:spring-cloud-stream-test-support" +---- +===== + +[[features-messaging-manual]] +==== Manual Integration Testing + +The main interface used by the tests is +`org.springframework.cloud.contract.verifier.messaging.MessageVerifier`. +It defines how to send and receive messages. You can create your own implementation to +achieve the same goal. + +In a test, you can inject a `ContractVerifierMessageExchange` to send and receive +messages that follow the contract. Then add `@AutoConfigureMessageVerifier` to your test. +Here's an example: + +[source,java,indent=0] +---- +@RunWith(SpringTestRunner.class) +@SpringBootTest +@AutoConfigureMessageVerifier +public static class MessagingContractTests { + + @Autowired + private MessageVerifier verifier; + ... +} +---- + +NOTE: If your tests require stubs as well, then `@AutoConfigureStubRunner` includes the +messaging configuration, so you only need the one annotation. + +[[features-messaging-test-generation]] +=== Messaging Producer Side Test Generation + +Having the `input` or `outputMessage` sections in your DSL results in creation of tests +on the publisher's side. By default, JUnit 4 tests are created. However, there is also a +possibility to create JUnit 5, TestNG or Spock tests. + +There are 3 main scenarios that we should take into consideration: + +* Scenario 1: There is no input message that produces an output message. The output +message is triggered by a component inside the application (for example, scheduler). +* Scenario 2: The input message triggers an output message. +* Scenario 3: The input message is consumed and there is no output message. + +IMPORTANT: The destination passed to `messageFrom` or `sentTo` can have different +meanings for different messaging implementations. For *Stream* and *Integration* it is +first resolved as a `destination` of a channel. Then, if there is no such `destination` +it is resolved as a channel name. For *Camel*, that's a certain component (for example, +`jms`). + +[[features-messaging-scenario1]] +==== Scenario 1: No Input Message + +For the given contract: + +===== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_dsl] +---- + +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml +[source,yml,indent=0] +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario1.yml[indent=0] +---- +===== + +The following test would be created + +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.JUnit +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_junit_test] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Spock +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_test] +---- +==== + +[[features-messaging-scenario2]] +==== Scenario 2: Output Triggered by Input + +For the given contract: + +===== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_dsl] +---- + +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario2.yml[indent=0] +---- +===== + +The following test would be created + +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.JUnit +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_junit] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Spock +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_spock] +---- +==== + +[[features-messaging-scenario3]] +==== Scenario 3: No Output Message + +For the given contract: + +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.groovy +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] +---- + +[source,yml,indent=0,subs="verbatim,attributes",role="secondary"] +.yml +---- +include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] +---- +==== + +The following test would be created + +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.JUnit +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_junit] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Spock +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_spock] +---- +==== + +[[features-messaging-consumer]] +=== Consumer Stub Generation + +Unlike the HTTP part, in messaging, we need to publish the contract definition inside the JAR with +a stub. Then it is parsed on the consumer side and proper stubbed routes are created. + +IMPORTANT: If you have multiple frameworks on the classpath Stub Runner will need to +define which one should be used. Let's assume that you have both AMQP, Spring Cloud Stream and Spring Integration +on the classpath. Then you need to set `stubrunner.stream.enabled=false` and `stubrunner.integration.enabled=false`. +That way the only remaining framework is Spring AMQP. + +[[features-messaging-stub-triggering]] +==== Stub triggering + +To trigger a message, use the `StubTrigger` interface: + +[source,groovy] +---- +include::{stubrunner_core_path}/src/main/java/org/springframework/cloud/contract/stubrunner/StubTrigger.java[lines=16..-1] +---- + +For convenience, the `StubFinder` interface extends `StubTrigger`, so you only need one +or the other in your tests. + +`StubTrigger` gives you the following options to trigger a message: + +* <> +* <> +* <> +* <> + +[[features-messaging-trigger-label]] +==== Trigger by Label + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger,indent=0] +---- +[[features-messaging-trigger-group-artifact-ids]] +==== Trigger by Group and Artifact Ids + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_group_artifact,indent=0] +---- + +[[features-messaging-trigger-artifact-ids]] +==== Trigger by Artifact Ids + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_artifact,indent=0] +---- + +[[features-messaging-trigger-all-messages]] +==== Trigger All Messages + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_all,indent=0] +---- + +:input_name: jms:input +:output_name: jms:output + +[[features-messaging-stub-runner-camel]] +=== Messaging Consumer Side With Apache Camel + +Spring Cloud Contract 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. + +[[features-messaging-stub-runner-camel-adding]] +==== Adding it to the project + +It's enough to have both Apache Camel and Spring Cloud Contract Stub Runner on classpath. +Remember to annotate your test class with `@AutoConfigureStubRunner`. + +[[features-messaging-stub-runner-camel-disabling]] +==== Disabling the functionality + +If you need to disable this functionality just pass `stubrunner.camel.enabled=false` property. + +[[features-messaging-stub-runner-camel-example]] +==== Examples + +Let us assume that we have the following Maven repository with a deployed stubs for the +`camelService` application. + +[source,bash,indent=0] +---- +└── .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: + +[source,bash,indent=0] +---- +├── META-INF +│   └── MANIFEST.MF +└── repository + ├── accurest + │   ├── bookDeleted.groovy + │   ├── bookReturned1.groovy + │   └── bookReturned2.groovy + └── mappings +---- + +Let's consider the following contracts (let' number it with *1*): + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl,indent=0] +---- + +and number *2* + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] +---- + +[[features-messaging-stub-runner-camel-scenario1]] +===== 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 + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger,indent=0] +---- + +Next we'll want to listen to the output of the message sent to `{output_name}` + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] +---- + +And the received message would pass the following assertions + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] +---- +[[features-messaging-stub-runner-camel-scenario2]] +===== Scenario 2 (output triggered by input) + +Since the route is set for you it's enough to just send a message to the `{output_name}` destination. + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_send,indent=0] +---- + +Next we'll want to listen to the output of the message sent to `{output_name}` + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive,indent=0] +---- + +And the received message would pass the following assertions + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive_message,indent=0] +---- + +[[features-messaging-stub-runner-camel-scenario3]] +===== Scenario 3 (input with no output) + +Since the route is set for you it's enough to just send a message to the `{output_name}` destination. + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] +---- + +:input_name: input +:output_name: output + +[[features-messaging-stub-runner-integration]] +=== Messaging Consumer Side With Spring Integration + +Spring Cloud Contract Stub Runner's messaging module gives you an easy way to +integrate with Spring Integration. For the provided artifacts, it automatically downloads +the stubs and registers the required routes. + +[[features-messaging-stub-runner-integration-adding]] +==== Adding the Runner to the Project + +You can have both Spring Integration and Spring Cloud Contract Stub Runner on the +classpath. Remember to annotate your test class with `@AutoConfigureStubRunner`. + +[[features-messaging-stub-runner-integration-disabling]] +==== Disabling the functionality + +If you need to disable this functionality, set the +`stubrunner.integration.enabled=false` property. + +[[features-messaging-stub-runner-integration-example]] +==== Examples + +Assume that you have the following Maven repository with deployed stubs for the +`integrationService` application: + +[source,bash,indent=0] +---- +└── .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 +---- + +Further assume the stubs contain the following structure: + +[source,bash,indent=0] +---- +├── META-INF +│   └── MANIFEST.MF +└── repository + ├── accurest + │   ├── bookDeleted.groovy + │   ├── bookReturned1.groovy + │   └── bookReturned2.groovy + └── mappings +---- + +Consider the following contracts (numbered *1*): + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=sample_dsl,indent=0] +---- + +Now consider *2*: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] +---- + +and the following Spring Integration Route: + +[source,xml] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/resources/integration-context.xml[lines=1;18..-1] +---- + +These examples lend themselves to three scenarios: + +* <> +* <> +* <> + +[[features-messaging-stub-runner-integration-scenario1]] +===== Scenario 1 (no input message) + +To trigger a message via the `return_book_1` label, use the `StubTigger` interface, as +follows: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger,indent=0] +---- + +To listen to the output of the message sent to `{output_name}`: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] +---- + +The received message would pass the following assertions: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] +---- + +[[features-messaging-stub-runner-integration-scenario2]] +===== Scenario 2 (output triggered by input) + +Since the route is set for you, you can send a message to the `{output_name}` +destination: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_send,indent=0] +---- + +To listen to the output of the message sent to `{output_name}`: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_receive,indent=0] +---- + +The received message passes the following assertions: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_receive_message,indent=0] +---- + +[[features-messaging-stub-runner-integration-scenario3]] +===== Scenario 3 (input with no output) + +Since the route is set for you, you can send a message to the `{input_name}` destination: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-integration/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] +---- + +[[features-messaging-stub-runner-stream]] +=== Messaging Consumer Side With Spring Cloud Stream + +Spring Cloud Contract Stub Runner's messaging module gives you an easy way to +integrate with Spring Stream. For the provided artifacts, it automatically downloads the +stubs and registers the required routes. + +WARNING: If Stub Runner's integration with Stream the `messageFrom` or `sentTo` Strings +are resolved first as a `destination` of a channel and no such `destination` exists, the +destination is resolved as a channel name. + +IMPORTANT: If you want to use Spring Cloud Stream remember, to add a dependency on +`org.springframework.cloud:spring-cloud-stream-test-support`. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + + org.springframework.cloud + spring-cloud-stream-test-support + test + +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +testCompile "org.springframework.cloud:spring-cloud-stream-test-support" +---- +==== + +[[features-messaging-stub-runner-stream-adding]] +==== Adding the Runner to the Project + +You can have both Spring Cloud Stream and Spring Cloud Contract Stub Runner on the +classpath. Remember to annotate your test class with `@AutoConfigureStubRunner`. + +[[features-messaging-stub-runner-stream-disabling]] +==== Disabling the functionality + +If you need to disable this functionality, set the `stubrunner.stream.enabled=false` +property. + +[[features-messaging-stub-runner-stream-example]] +==== Examples + +Assume that you have the following Maven repository with a deployed stubs for the +`streamService` application: + +[source,bash,indent=0] +---- +└── .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 +---- + +Further assume the stubs contain the following structure: + +[source,bash,indent=0] +---- +├── META-INF +│   └── MANIFEST.MF +└── repository + ├── accurest + │   ├── bookDeleted.groovy + │   ├── bookReturned1.groovy + │   └── bookReturned2.groovy + └── mappings +---- + +Consider the following contracts (numbered *1*): + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=sample_dsl,indent=0] +---- + +Now consider *2*: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] +---- + +Now consider the following Spring configuration: + +[source,yaml] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/resources/application.yml[] +---- + +These examples lend themselves to three scenarios: + +* <> +* <> +* <> + +[[features-messaging-stub-runner-stream-scenario1]] +===== Scenario 1 (no input message) + +To trigger a message via the `return_book_1` label, use the `StubTrigger` interface as +follows: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger,indent=0] +---- + +To listen to the output of the message sent to a channel whose `destination` is +`returnBook`: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] +---- + +The received message passes the following assertions: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] +---- + +[[features-messaging-stub-runner-stream-scenario2]] +===== Scenario 2 (output triggered by input) + +Since the route is set for you, you can send a message to the `bookStorage` +`destination`: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_send,indent=0] +---- + +To listen to the output of the message sent to `returnBook`: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_receive,indent=0] +---- + +The received message passes the following assertions: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_receive_message,indent=0] +---- + +[[features-messaging-stub-runner-stream-scenario3]] +===== Scenario 3 (input with no output) + +Since the route is set for you, you can send a message to the `{output_name}` +destination: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] +---- + +[[features-messaging-stub-runner-amqp]] +=== Messaging Consumer Side With Spring AMQP + +Spring Cloud Contract Stub Runner's messaging module provides an easy way to +integrate with Spring AMQP's Rabbit Template. For the provided artifacts, it +automatically downloads the stubs and registers the required routes. + +The integration tries to work standalone (that is, without interaction with a running +RabbitMQ message broker). It expects a `RabbitTemplate` on the application context and +uses it as a spring boot test named `@SpyBean`. As a result, it can use the mockito spy +functionality to verify and inspect messages sent by the application. + +On the message consumer side, the stub runner considers all `@RabbitListener` annotated +endpoints and all `SimpleMessageListenerContainer` objects on the application context. + +As messages are usually sent to exchanges in AMQP, the message contract contains the +exchange name as the destination. Message listeners on the other side are bound to +queues. Bindings connect an exchange to a queue. If message contracts are triggered, the +Spring AMQP stub runner integration looks for bindings on the application context that +match this exchange. Then it collects the queues from the Spring exchanges and tries to +find message listeners bound to these queues. The message is triggered for all matching +message listeners. + +If you need to work with routing keys, it's enough to pass them via the `amqp_receivedRoutingKey` +messaging header. + +[[features-messaging-stub-runner-amqp-adding]] +==== Adding the Runner to the Project + +You can have both Spring AMQP and Spring Cloud Contract Stub Runner on the classpath and +set the property `stubrunner.amqp.enabled=true`. Remember to annotate your test class +with `@AutoConfigureStubRunner`. + +IMPORTANT: If you already have Stream and Integration on the classpath, you need +to disable them explicitly by setting the `stubrunner.stream.enabled=false` and +`stubrunner.integration.enabled=false` properties. + +[[features-messaging-stub-runner-amqp-example]] +==== Examples + +Assume that you have the following Maven repository with a deployed stubs for the +`spring-cloud-contract-amqp-test` application. + +[source,bash,indent=0] +---- +└── .m2 + └── repository + └── com + └── example + └── spring-cloud-contract-amqp-test + ├── 0.4.0-SNAPSHOT + │   ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom + │   ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar + │   └── maven-metadata-local.xml + └── maven-metadata-local.xml +---- + +Further assume that the stubs contain the following structure: + +[source,bash,indent=0] +---- +├── META-INF +│   └── MANIFEST.MF +└── contracts + └── shouldProduceValidPersonData.groovy +---- + +Consider the following contract: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy[tags=amqp_contract,indent=0] +---- + +Now consider the following Spring configuration: + +[source,yaml] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/test/resources/application.yml[] +---- + +[[features-messaging-stub-runner-amqp-triggering]] +===== Triggering the message + +To trigger a message using the contract above, use the `StubTrigger` interface as +follows: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy[tags=client_trigger,indent=0] +---- + +The message has a destination of `contract-test.exchange`, so the Spring AMQP stub runner +integration looks for bindings related to this exchange. + +[source,java] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpMessagingApplication.java[tags=amqp_binding,indent=0] +---- + +The binding definition binds the queue `test.queue`. As a result, the following listener +definition is matched and invoked with the contract message. + +[source,java] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpMessagingApplication.java[tags=amqp_listener,indent=0] +---- + +Also, the following annotated listener matches and is invoked: +[source,java] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-amqp/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/MessageSubscriberRabbitListener.java[tags=amqp_annotated_listener,indent=0] +---- + +NOTE: The message is directly handed over to the `onMessage` method of the +`MessageListener` associated with the matching `SimpleMessageListenerContainer`. + +[[features-messaging-stub-runner-amqp-configuration]] +===== Spring AMQP Test Configuration + +In order to avoid Spring AMQP trying to connect to a running broker during our tests +configure a mock `ConnectionFactory`. + +To disable the mocked ConnectionFactory, set the following property: +`stubrunner.amqp.mockConnection=false` + +[source,yaml] +---- +stubrunner: + amqp: + mockConnection: false +---- \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/README.adoc b/docs/src/main/asciidoc/_project-features-stubrunner.adoc similarity index 70% rename from spring-cloud-contract-stub-runner/README.adoc rename to docs/src/main/asciidoc/_project-features-stubrunner.adoc index e973a7caa7..5d64dc5bef 100644 --- a/spring-cloud-contract-stub-runner/README.adoc +++ b/docs/src/main/asciidoc/_project-features-stubrunner.adoc @@ -1,14 +1,74 @@ -:branch: master -:samples_branch: 2.2.x +[[features-stub-runner-features-stub-runner]] +== Spring Cloud Contract Stub Runner +One of the issues that you might encounter while using Spring Cloud Contract Verifier is +passing the generated WireMock JSON stubs from the server side to the client side (or to +various clients). The same takes place in terms of client-side generation for messaging. + +Copying the JSON files and setting the client side for messaging manually is out of the +question. That is why we introduced Spring Cloud Contract Stub Runner. It can +automatically download and run the stubs for you. + +[[features-stub-runner-features-stub-runner-snapshot-versions]] +=== Snapshot versions + +Add the additional snapshot repository to your `build.gradle` file to use snapshot +versions, which are automatically uploaded after every successful build: + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{standalone_samples_path}/http-server/pom.xml[tags=repos,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{standalone_samples_path}/http-server/build.gradle[tags=repos,indent=0] +---- +==== + +[[features-stub-runner-publishing-stubs-as-jars]] +=== 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. + +TIP: For both Maven and Gradle, the setup comes ready to work. However, you can customize +it if you want to. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- + +include::{samples_url}/producer_with_restdocs/pom.xml[tags=skip_jar,indent=0] + + +include::{samples_url}/producer_with_restdocs/pom.xml[tags=assembly,indent=0] + + +include::{samples_url}/producer_with_restdocs/src/assembly/stub.xml[indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=jar_setup,indent=0] +---- +==== + +[[features-stub-runner-core]] === Stub Runner Core -Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of +Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of https://martinfowler.com/articles/consumerDrivenContracts.html[Consumer Driven Contracts]. Stub Runner allows you to automatically download the stubs of the provided dependencies (or pick those from the classpath), start WireMock servers for them and feed them with proper stub definitions. For messaging, special stub routes are defined. +[[features-stub-runner-retrieving]] ==== Retrieving stubs You can pick the following options of acquiring stubs @@ -17,8 +77,10 @@ You can pick the following options of acquiring stubs - Classpath scanning solution that searches classpath via pattern to retrieve stubs - Write your own implementation of the `org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder` for full customization +// TODO: Fix link The latter example is described in the <> section. +[[features-stub-runner-downloading-stub]] ===== Stub downloading You can control the stub downloading via the `stubsMode` switch. It picks value from the @@ -35,6 +97,7 @@ Example: @AutoConfigureStubRunner(repositoryRoot="https://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095", stubsMode = StubRunnerProperties.StubsMode.LOCAL) ---- +[[features-stub-runner-classpath-scanning]] ===== Classpath scanning If you set the `stubsMode` property to `StubRunnerProperties.StubsMode.CLASSPATH` @@ -51,6 +114,7 @@ Let's look at the following example: If you've added the dependencies to your classpath +==== [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- @@ -92,6 +156,7 @@ testCompile("com.example.foo:bar:1.0.0:superstubs") { transitive = false } ---- +==== Then the following locations on your classpath will get scanned. For `com.example:beer-api-producer-restdocs` @@ -146,6 +211,7 @@ structure in your stubs jar. By maintaining this structure classpath gets scanned and you can profit from the messaging / HTTP stubs without the need to download artifacts. +[[features-stub-runner-configuring-http-server-stubs]] ===== Configuring HTTP Server Stubs Stub Runner has a notion of a `HttpServerStub` that abstracts the underlaying @@ -165,57 +231,22 @@ case might be starting WireMock for the given artifact id, on an HTTPs port. Exa .WireMockHttpServerStubConfigurer implementation [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=wireMockHttpServerStubConfigurer] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=wireMockHttpServerStubConfigurer] ---- You can then reuse it via the annotation [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=annotation] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=annotation] ---- Whenever an https port is found, it will take precedence over the http one. +[[features-stub-runner-running-stubs]] ==== Running stubs -===== Running using main app - -You can set the following options to the main class: - -[source,groovy,indent=0] ----- --c, --classifier 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) ---maxPort, --maxp Maximum port value to be assigned to - the WireMock instance. Defaults to - 15000 (default: 15000) ---minPort, --minp Minimum port value to be assigned to - the WireMock instance. Defaults to - 10000 (default: 10000) --p, --password Password to user when connecting to - repository ---phost, --proxyHost Proxy host to use for repository - requests ---pport, --proxyPort [Integer] Proxy port to use for repository - requests --r, --root Location of a Jar containing server - where you keep your stubs (e.g. http: - //nexus. - net/content/repositories/repository) --s, --stubs Comma separated list of Ivy - representation of jars with stubs. - Eg. groupid:artifactid1,groupid2: - artifactid2:classifier ---sm, --stubsMode Stubs mode to be used. Acceptable values - [CLASSPATH, LOCAL, REMOTE] --u, --username Username to user when connecting to - repository ----- - +[[features-stub-runner-http-stubs]] ===== HTTP Stubs Stubs are defined in JSON documents, whose syntax is defined in http://wiremock.org/stubbing.html[WireMock documentation] @@ -239,6 +270,7 @@ Example: } ---- +[[features-stub-runner-viewing]] ===== Viewing registered mappings Every stubbed collaborator exposes list of defined mappings under `__/admin/` endpoint. @@ -294,17 +326,19 @@ mappings available for the given server: ] ---- +[[features-stub-runner-messaging]] ===== Messaging Stubs Depending on the provided Stub Runner dependency and the DSL the messaging routes are automatically set up. +[[features-stub-runner-junit]] === Stub Runner JUnit Rule and Stub Runner JUnit5 Extension 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] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=classrule] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=classrule] ---- There's also a `StubRunnerExtension` available for JUnit 5. `StubRunnerRule` and `StubRunnerExtension` work in a very @@ -317,74 +351,79 @@ similar fashion. After the rule/ extension is executed, Stub Runner connects to - feed the WireMock server with all JSON files that are valid WireMock definitions - can also send messages (remember to pass an implementation of `MessageVerifier` interface) -Stub Runner uses https://wiki.eclipse.org/Aether[Eclipse Aether] mechanism to download the Maven dependencies. +Stub Runner uses https://wiki.eclipse.org/Aether[Eclipse Aether] mechanism to download the Maven dependencies. Check their https://wiki.eclipse.org/Aether[docs] for more information. Since the `StubRunnerRule` and `StubRunnerExtension` implement the `StubFinder` they allow you to find the started stubs: [source,groovy,indent=0] ---- -include::src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java[lines=16..-1] +include::{stubrunner_core_path}/src/main/java/org/springframework/cloud/contract/stubrunner/StubFinder.java[lines=16..-1] ---- -Example of usage in Spock tests: -[source,groovy,indent=0] +==== +[source,groovy,indent=0,subs="verbatim,attributes",role="primary"] +.spock ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleSpec.groovy[tags=classrule] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleSpec.groovy[tags=classrule] ---- -Example of usage in JUnit tests: - -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,attributes",role="secondary"] +.junit 4 ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=test] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java[tags=test] ---- -JUnit 5 Extension example: - -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,attributes",role="secondary"] +.junit 5 ---- -include::src/test/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerJUnit5ExtensionTests.java[tags=extension] +include::{stubrunner_core_path}/src/test/java/org/springframework/cloud/contract/stubrunner/junit/StubRunnerJUnit5ExtensionTests.java[tags=extension] ---- +==== +//TODO: Fix link Check the *Common properties for JUnit and Spring* for more information on how to apply global configuration of Stub Runner. IMPORTANT: To use the JUnit rule or JUnit 5 extension together with messaging, you have to provide an implementation of the `MessageVerifier` interface to the rule builder (e.g. `rule.messageVerifier(new MyMessageVerifier())`). If you don't do this, then whenever you try to send a message an exception will be thrown. +[[features-stub-runner-rule-maven-settings]] ==== Maven settings The stub downloader honors Maven settings for a different local repository folder. Authentication details for repositories and profiles are currently not taken into account, so you need to specify it using the properties mentioned above. +[[features-stub-runner-rule-fixed-ports]] ==== 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. +[[features-stub-runner-rule-fluent-api]] ==== Fluent API When using the `StubRunnerRule` or `StubRunnerExtension` you can add a stub to download and then pass the port for the last downloaded stub. [source,java,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=classrule_with_port] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=classrule_with_port] ---- You can see that for this example the following test is valid: [source,java,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=test_with_port] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleCustomPortJUnitTest.java[tags=test_with_port] ---- +[[features-stub-runner-rule-spring]] ==== Stub Runner with 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 +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 @@ -392,14 +431,14 @@ its methods as presented below: [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=test] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy[tags=test] ---- for the following configuration file: [source,yml,indent=0] ---- -include::src/test/resources/application-test.yml[tags=test] +include::{stubrunner_core_path}/src/test/resources/application-test.yml[tags=test] ---- Instead of using the properties you can also use the properties inside the `@AutoConfigureStubRunner`. @@ -407,7 +446,7 @@ Below you can find an example of achieving the same result by setting values on [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=autoconfigure] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=autoconfigure] ---- Stub Runner Spring registers environment variables in the following manner @@ -433,6 +472,7 @@ int fooPort; int barPort; ---- +[[features-stub-runner-cloud]] === Stub Runner Spring Cloud Stub Runner can integrate with Spring Cloud. @@ -442,6 +482,7 @@ For real life examples you can check the - https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/producer[producer app sample] - https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/consumer_with_discovery[consumer app sample] +[[features-stub-runner-cloud-stubbing-discovery]] ==== Stubbing Service Discovery The most important feature of `Stub Runner Spring Cloud` is the fact that it's stubbing @@ -457,16 +498,17 @@ For example this test will pass [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=test] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy[tags=test] ---- for the following configuration file [source,yml,indent=0] ---- -include::src/test/resources/application.yml[tags=ids] +include::{stubrunner_core_path}/src/test/resources/application.yml[tags=ids] ---- +[[features-stub-runner-cloud-stubbing-profiles]] ===== Test profiles and service discovery In your integration tests you typically don't want to call neither a discovery service (e.g. Eureka) @@ -485,6 +527,7 @@ via a static block like presented below (example for Eureka) } ---- +[[features-stub-runner-additional-config]] ==== Additional Configuration You can match the artifactId of the stub with the name of your app by using the `stubrunner.idsToServiceIds:` map. @@ -503,6 +546,7 @@ via the following system properties or environment variables - `org.apache.maven.user-settings` - path to custom maven user settings location - `org.apache.maven.global-settings` - path to maven global settings location +[[features-stub-runner-boot]] === Stub Runner Boot Application Spring Cloud Contract Stub Runner Boot is a Spring Boot application that exposes REST endpoints to @@ -512,8 +556,10 @@ One of the use-cases is to run some smoke (end to end) tests on a deployed appli You can check out the https://github.com/spring-cloud/spring-cloud-pipelines[Spring Cloud Pipelines] project for more information. +[[features-stub-runner-boot-how]] ==== How to use it? +[[features-stub-runner-boot-server]] ===== Stub Runner Server Just add the @@ -527,6 +573,7 @@ Annotate a class with `@EnableStubRunnerServer`, build a fat-jar and you're read For the properties check the *Stub Runner Spring* section. +[[features-stub-runner-boot-how-fat-jar]] ===== Stub Runner Server Fat Jar You can download a standalone JAR from Maven (e.g. for version 2.0.1.RELEASE), as follows: @@ -537,6 +584,7 @@ $ wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/s $ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=... ---- +[[features-stub-runner-boot-how-cli]] ===== Spring Cloud CLI Starting from `1.4.0.RELEASE` version of the https://cloud.spring.io/spring-cloud-cli[Spring Cloud CLI] @@ -558,13 +606,16 @@ stubrunner: and then just call `spring cloud stubrunner` from your terminal window to start the Stub Runner server. It will be available at port `8750`. +[[features-stub-runner-boot-endpoints]] ==== Endpoints +[[features-stub-runner-boot-endpoints-http]] ===== 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) +[[features-stub-runner-boot-endpoints-messaging]] ===== Messaging For Messaging @@ -573,13 +624,15 @@ For Messaging - 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) +[[features-stub-runner-boot-endpoints-example]] ==== Example [source,groovy,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy[tags=boot_usage] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy[tags=boot_usage] ---- +[[features-stub-runner-boot-service-discovery]] ==== Stub Runner Boot with Service Discovery One of the possibilities of using Stub Runner Boot is to use it as a feed of stubs for "smoke-tests". What does it mean? @@ -597,7 +650,7 @@ The problem with this approach is such that if you're doing microservices most l [source,java,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka] ---- As you can see we want to start a Stub Runner Boot server `@EnableStubRunnerServer`, enable Eureka client `@EnableEurekaClient` @@ -609,7 +662,7 @@ Now let's assume that we want to start this application so that the stubs get au [source,bash,indent=0] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka_args] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java[tags=stubrunnereureka_args] ---- That way your deployed application can send requests to started WireMock servers via the service @@ -617,7 +670,8 @@ discovery. Most likely points 1-3 could be set by default in `application.yml` c likely to change. That way you can provide only the list of stubs to download whenever you start the Stub Runner Boot. -=== Stubs Per Consumer +[[features-stub-runner-stubs-per-consumer]] +=== Consumer-Driven Contracts: Stubs Per Consumer There are cases in which 2 consumers of the same endpoint want to have 2 different responses. @@ -687,7 +741,7 @@ Or set the test as follows: [source,groovy] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy[tags=test] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerSpec.groovy[tags=test] ... } ---- @@ -699,7 +753,7 @@ Or set the consumer name explicitly [source,groovy] ---- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy[tags=test] +include::{stubrunner_core_path}/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerStubsPerConsumerWithConsumerNameSpec.groovy[tags=test] ... } ---- @@ -708,4 +762,63 @@ Then only the stubs registered under a path that contains the `foo-consumer` in `src/test/resources/contracts/foo-consumer/some/contracts/...` folder) will be allowed to be referenced. You can check out https://github.com/spring-cloud/spring-cloud-contract/issues/224[issue 224] for more -information about the reasons behind this change. \ No newline at end of file +information about the reasons behind this change. + +[[features-stub-runner-common]] +=== Common + +This section briefly describes common properties, including: + +* <> +* <> + +[[features-stub-runner-common-properties-junit-spring]] +==== Common Properties for JUnit and Spring + +You can set repetitive properties by using system properties or Spring configuration +properties. Here are their names with their default values: + +[frame="topbot",options="header"] +|=============== +| Property name | Default value | Description +|stubrunner.minPort|10000| Minimum value of a port for a started WireMock with stubs. +|stubrunner.maxPort|15000| Maximum value of a port for a started WireMock with stubs. +|stubrunner.repositoryRoot|| Maven repo URL. If blank, then call the local maven repo. +|stubrunner.classifier|stubs| Default classifier for the stub artifacts. +|stubrunner.stubsMode|CLASSPATH| The way you want to fetch and register the stubs +|stubrunner.ids|| Array of Ivy notation stubs to download. +|stubrunner.username|| Optional username to access the tool that stores the JARs with +stubs. +|stubrunner.password|| Optional password to access the tool that stores the JARs with +stubs. +|stubrunner.stubsPerConsumer|false| Set to `true` if you want to use different stubs for +each consumer instead of registering all stubs for every consumer. +|stubrunner.consumerName|| If you want to use a stub for each consumer and want to +override the consumer name just change this value. +|=============== + +[[features-stub-runner-stub-runner-stub-ids]] +==== Stub Runner Stubs IDs + +You can provide the stubs to download via the `stubrunner.ids` system property. They +follow this pattern: + +[source,java,indent=0] +---- +groupId:artifactId:version:classifier:port +---- + +Note that `version`, `classifier` and `port` are optional. + +* If you do not provide the `port`, a random one will be picked. +* If you do not provide the `classifier`, the default is used. (Note that you can +pass an empty classifier this way: `groupId:artifactId:version:`). +* If you do not provide the `version`, then the `+` will be passed and the latest one is +downloaded. + +`port` means the port of the WireMock server. + +IMPORTANT: Starting with version 1.0.4, you can provide a range of versions that you +would like the Stub Runner to take into consideration. You can read more about the +https://wiki.eclipse.org/Aether/New_and_Noteworthy#Version_Ranges[Aether versioning +ranges here]. \ No newline at end of file diff --git a/docs/src/main/asciidoc/_project-features-wiremock.adoc b/docs/src/main/asciidoc/_project-features-wiremock.adoc new file mode 100644 index 0000000000..3a8477ce0b --- /dev/null +++ b/docs/src/main/asciidoc/_project-features-wiremock.adoc @@ -0,0 +1,175 @@ +[[features-wiremock]] +== Spring Cloud Contract WireMock +include::_attributes.adoc[] + +The Spring Cloud Contract WireMock modules let you use https://github.com/tomakehurst/wiremock[WireMock] in a +Spring Boot application. Check out the +https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples[samples] +for more details. + +If you have a Spring Boot application that uses Tomcat as an embedded server (which is +the default with `spring-boot-starter-web`), you can add +`spring-cloud-starter-contract-stub-runner` to your classpath and add `@AutoConfigureWireMock` in +order to be able to use Wiremock in your tests. Wiremock runs as a stub server and you +can register stub behavior using a Java API or via static JSON declarations as part of +your test. The following code shows an example: + +[source,java,indent=0] +---- +include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test1] +include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test2] +---- + +To start the stub server on a different port use (for example), +`@AutoConfigureWireMock(port=9999)`. For a random port, use a value of `0`. The stub +server port can be bound in the test application context with the "wiremock.server.port" +property. Using `@AutoConfigureWireMock` adds a bean of type `WiremockConfiguration` to +your test application context, where it will be cached in between methods and classes +having the same context, the same as for Spring integration tests. Also you can inject a bean of type `WireMockServer` into your test. + +[[features-wiremock-registering-stubs]] +=== Registering Stubs Automatically + +If you use `@AutoConfigureWireMock`, it registers WireMock JSON stubs from the file +system or classpath (by default, from `file:src/test/resources/mappings`). You can +customize the locations using the `stubs` attribute in the annotation, which can be an +Ant-style resource pattern or a directory. In the case of a directory, `**/*.json` is +appended. The following code shows an example: + +---- +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureWireMock(stubs="classpath:/stubs") +public class WiremockImportApplicationTests { + + @Autowired + private Service service; + + @Test + public void contextLoads() throws Exception { + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} +---- + +NOTE: Actually, WireMock always loads mappings from `src/test/resources/mappings` *as +well as* the custom locations in the stubs attribute. To change this behavior, you can +also specify a files root as described in the next section of this document. + +If you're using Spring Cloud Contract's default stub jars, then your +stubs are stored under `/META-INF/group-id/artifact-id/versions/mappings/` folder. If you want to register all stubs from that location, from all embedded JARs, then it's enough to use the following syntax. + +[source,java,indent=0] +---- +include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockFilesApplicationWithUrlResourceTests.java[tags=load_all_stubs] +---- + +[[features-wiremock-using-files]] +=== Using Files to Specify the Stub Bodies + +WireMock can read response bodies from files on the classpath or the file system. In that +case, you can see in the JSON DSL that the response has a `bodyFileName` instead of a +(literal) `body`. The files are resolved relative to a root directory (by default, +`src/test/resources/\__files`). To customize this location you can set the `files` +attribute in the `@AutoConfigureWireMock` annotation to the location of the parent +directory (in other words, `__files` is a subdirectory). You can use Spring resource +notation to refer to `file:...` or `classpath:...` locations. Generic URLs are not +supported. A list of values can be given, in which case WireMock resolves the first file +that exists when it needs to find a response body. + +NOTE: When you configure the `files` root, it also affects the +automatic loading of stubs, because they come from the root location +in a subdirectory called "mappings". The value of `files` has no +effect on the stubs loaded explicitly from the `stubs` attribute. + +[[features-wiremock-junit-rule]] +=== Alternative: Using JUnit Rules + +For a more conventional WireMock experience, you can use JUnit `@Rules` to start and stop +the server. To do so, use the `WireMockSpring` convenience class to obtain an `Options` +instance, as shown in the following example: + +[source,java,indent=0] +---- +include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test1] +include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test2] +---- + +The `@ClassRule` means that the server shuts down after all the methods in this class +have been run. + +[[features-wiremock-relaxed-ssl]] +=== Relaxed SSL Validation for Rest Template + +WireMock lets you stub a "secure" server with an "https" URL protocol. If your +application wants to contact that stub server in an integration test, it will find that +the SSL certificates are not valid (the usual problem with self-installed certificates). +The best option is often to re-configure the client to use "http". If that's not an +option, you can ask Spring to configure an HTTP client that ignores SSL validation errors +(do so only for tests, of course). + +To make this work with minimum fuss, you need to be using the Spring Boot +`RestTemplateBuilder` in your app, as shown in the following example: + +[source,java,indent=0] +---- + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } +---- + +You need `RestTemplateBuilder` because the builder is passed through callbacks to +initialize it, so the SSL validation can be set up in the client at that point. This +happens automatically in your test if you are using the `@AutoConfigureWireMock` +annotation or the stub runner. If you use the JUnit `@Rule` approach, you need to add the +`@AutoConfigureHttpClient` annotation as well, as shown in the following example: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest("app.baseUrl=https://localhost:6443") +@AutoConfigureHttpClient +public class WiremockHttpsServerApplicationTests { + + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().httpsPort(6443)); +... +} +---- + +If you are using `spring-boot-starter-test`, you have the Apache HTTP client on the +classpath and it is selected by the `RestTemplateBuilder` and configured to ignore SSL +errors. If you use the default `java.net` client, you do not need the annotation (but it +won't do any harm). There is no support currently for other clients, but it may be added +in future releases. + +To disable the custom `RestTemplateBuilder`, set the `wiremock.rest-template-ssl-enabled` +property to `false`. + +[[features-wiremock-spring-mvc-mocks]] +=== WireMock and Spring MVC Mocks + +Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into +a Spring `MockRestServiceServer`. The following code shows an example: + +[source,java,indent=0] +---- +include::{doc_samples}/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java[tags=wiremock_test] +---- + +The `baseUrl` value is prepended to all mock calls, and the `stubs()` method takes a stub +path resource pattern as an argument. In the preceding example, the stub defined at +`/stubs/resource.json` is loaded into the mock server. If the `RestTemplate` is asked to +visit `https://example.org/`, it gets the responses as being declared at that URL. More +than one stub pattern can be specified, and each one can be a directory (for a recursive +list of all ".json"), a fixed filename (as in the example above), or an Ant-style +pattern. The JSON format is the normal WireMock format, which you can read about in the +https://wiremock.org/docs/stubbing/[WireMock website]. + +Currently, the Spring Cloud Contract Verifier supports Tomcat, Jetty, and Undertow as +Spring Boot embedded servers, and Wiremock itself has "native" support for a particular +version of Jetty (currently 9.2). To use the native Jetty, you need to add the native +Wiremock dependencies and exclude the Spring Boot container (if there is one). \ No newline at end of file diff --git a/docs/src/main/asciidoc/_spring-cloud-contract-verifier.adoc b/docs/src/main/asciidoc/_spring-cloud-contract-verifier.adoc deleted file mode 100644 index 484b905565..0000000000 --- a/docs/src/main/asciidoc/_spring-cloud-contract-verifier.adoc +++ /dev/null @@ -1,13 +0,0 @@ -include::_verifier_introduction.adoc[] - -include::_verifier_faq.adoc[] - -include::_verifier_setup.adoc[] - -include::_verifier_messaging.adoc[] - -include::_verifier_stubrunner.adoc[] - -include::_verifier_stubrunner_msg.adoc[] - -include::_verifier_contract.adoc[] \ No newline at end of file diff --git a/docs/src/main/asciidoc/_spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/_spring-cloud-wiremock.adoc deleted file mode 100644 index e4203362c6..0000000000 --- a/docs/src/main/asciidoc/_spring-cloud-wiremock.adoc +++ /dev/null @@ -1,422 +0,0 @@ -:core_path: ../../.. -:doc_samples: {core_path}/samples/wiremock-jetty -:wiremock_tests: {core_path}/spring-cloud-contract-wiremock - -== Spring Cloud Contract WireMock - -The Spring Cloud Contract WireMock modules let you use https://github.com/tomakehurst/wiremock[WireMock] in a -Spring Boot application. Check out the -https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples[samples] -for more details. - -If you have a Spring Boot application that uses Tomcat as an embedded server (which is -the default with `spring-boot-starter-web`), you can add -`spring-cloud-starter-contract-stub-runner` to your classpath and add `@AutoConfigureWireMock` in -order to be able to use Wiremock in your tests. Wiremock runs as a stub server and you -can register stub behavior using a Java API or via static JSON declarations as part of -your test. The following code shows an example: - -[source,java,indent=0] ----- -include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test1] -include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test2] ----- - -To start the stub server on a different port use (for example), -`@AutoConfigureWireMock(port=9999)`. For a random port, use a value of `0`. The stub -server port can be bound in the test application context with the "wiremock.server.port" -property. Using `@AutoConfigureWireMock` adds a bean of type `WiremockConfiguration` to -your test application context, where it will be cached in between methods and classes -having the same context, the same as for Spring integration tests. Also you can inject a bean of type `WireMockServer` into your test. - -=== Registering Stubs Automatically - -If you use `@AutoConfigureWireMock`, it registers WireMock JSON stubs from the file -system or classpath (by default, from `file:src/test/resources/mappings`). You can -customize the locations using the `stubs` attribute in the annotation, which can be an -Ant-style resource pattern or a directory. In the case of a directory, `**/*.json` is -appended. The following code shows an example: - ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWireMock(stubs="classpath:/stubs") -public class WiremockImportApplicationTests { - - @Autowired - private Service service; - - @Test - public void contextLoads() throws Exception { - assertThat(this.service.go()).isEqualTo("Hello World!"); - } - -} ----- - -NOTE: Actually, WireMock always loads mappings from `src/test/resources/mappings` *as -well as* the custom locations in the stubs attribute. To change this behavior, you can -also specify a files root as described in the next section of this document. - -If you're using Spring Cloud Contract's default stub jars, then your -stubs are stored under `/META-INF/group-id/artifact-id/versions/mappings/` folder. If you want to register all stubs from that location, from all embedded JARs, then it's enough to use the following syntax. - -[source,java,indent=0] ----- -include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockFilesApplicationWithUrlResourceTests.java[tags=load_all_stubs] ----- - -=== Using Files to Specify the Stub Bodies - -WireMock can read response bodies from files on the classpath or the file system. In that -case, you can see in the JSON DSL that the response has a `bodyFileName` instead of a -(literal) `body`. The files are resolved relative to a root directory (by default, -`src/test/resources/\__files`). To customize this location you can set the `files` -attribute in the `@AutoConfigureWireMock` annotation to the location of the parent -directory (in other words, `__files` is a subdirectory). You can use Spring resource -notation to refer to `file:...` or `classpath:...` locations. Generic URLs are not -supported. A list of values can be given, in which case WireMock resolves the first file -that exists when it needs to find a response body. - -NOTE: When you configure the `files` root, it also affects the -automatic loading of stubs, because they come from the root location -in a subdirectory called "mappings". The value of `files` has no -effect on the stubs loaded explicitly from the `stubs` attribute. - -=== Alternative: Using JUnit Rules - -For a more conventional WireMock experience, you can use JUnit `@Rules` to start and stop -the server. To do so, use the `WireMockSpring` convenience class to obtain an `Options` -instance, as shown in the following example: - -[source,java,indent=0] ----- -include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test1] -include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test2] ----- - -The `@ClassRule` means that the server shuts down after all the methods in this class -have been run. - -=== Relaxed SSL Validation for Rest Template - -WireMock lets you stub a "secure" server with an "https" URL protocol. If your -application wants to contact that stub server in an integration test, it will find that -the SSL certificates are not valid (the usual problem with self-installed certificates). -The best option is often to re-configure the client to use "http". If that's not an -option, you can ask Spring to configure an HTTP client that ignores SSL validation errors -(do so only for tests, of course). - -To make this work with minimum fuss, you need to be using the Spring Boot -`RestTemplateBuilder` in your app, as shown in the following example: - -[source,java,indent=0] ----- - @Bean - public RestTemplate restTemplate(RestTemplateBuilder builder) { - return builder.build(); - } ----- - -You need `RestTemplateBuilder` because the builder is passed through callbacks to -initialize it, so the SSL validation can be set up in the client at that point. This -happens automatically in your test if you are using the `@AutoConfigureWireMock` -annotation or the stub runner. If you use the JUnit `@Rule` approach, you need to add the -`@AutoConfigureHttpClient` annotation as well, as shown in the following example: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest("app.baseUrl=https://localhost:6443") -@AutoConfigureHttpClient -public class WiremockHttpsServerApplicationTests { - - @ClassRule - public static WireMockClassRule wiremock = new WireMockClassRule( - WireMockSpring.options().httpsPort(6443)); -... -} ----- - -If you are using `spring-boot-starter-test`, you have the Apache HTTP client on the -classpath and it is selected by the `RestTemplateBuilder` and configured to ignore SSL -errors. If you use the default `java.net` client, you do not need the annotation (but it -won't do any harm). There is no support currently for other clients, but it may be added -in future releases. - -To disable the custom `RestTemplateBuilder`, set the `wiremock.rest-template-ssl-enabled` -property to `false`. - -=== WireMock and Spring MVC Mocks - -Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into -a Spring `MockRestServiceServer`. The following code shows an example: - -[source,java,indent=0] ----- -include::{doc_samples}/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java[tags=wiremock_test] ----- - -The `baseUrl` value is prepended to all mock calls, and the `stubs()` method takes a stub -path resource pattern as an argument. In the preceding example, the stub defined at -`/stubs/resource.json` is loaded into the mock server. If the `RestTemplate` is asked to -visit `https://example.org/`, it gets the responses as being declared at that URL. More -than one stub pattern can be specified, and each one can be a directory (for a recursive -list of all ".json"), a fixed filename (as in the example above), or an Ant-style -pattern. The JSON format is the normal WireMock format, which you can read about in the -https://wiremock.org/docs/stubbing/[WireMock website]. - -Currently, the Spring Cloud Contract Verifier supports Tomcat, Jetty, and Undertow as -Spring Boot embedded servers, and Wiremock itself has "native" support for a particular -version of Jetty (currently 9.2). To use the native Jetty, you need to add the native -Wiremock dependencies and exclude the Spring Boot container (if there is one). - -=== Customization of WireMock configuration - -You can register a bean of `org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer` type -in order to customize the WireMock configuration (e.g. add custom transformers). -Example: - -[source,java,indent=0] ----- -include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_1] -// perform your customization here -include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_2] ----- - -=== Generating Stubs using REST Docs - -https://projects.spring.io/spring-restdocs[Spring REST Docs] can be used to generate -documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc -or `WebTestClient` or Rest Assured. At the same time that you generate documentation for your API, you can also -generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your -normal REST Docs test cases and use `@AutoConfigureRestDocs` to have stubs be -automatically generated in the REST Docs output directory. - -[plantuml, rest-docs, png] ----- -"API Producer"->"API Producer": add Spring Cloud Contract (SCC) \nStub Runner dependency -"API Producer"->"API Producer": setup stub jar assembly -"API Producer"->"API Producer": write and setup REST Docs tests -"API Producer"->"Build": run build -"Build"->"REST Docs": generate API \ndocumentation -"REST Docs"->"SCC": generate stubs from the \nREST Docs tests -"REST Docs"->"SCC": generate contracts from the \nREST Docs tests -"Build"->"Build": assemble stubs jar with \nstubs and contracts -"Build"->"Nexus / Artifactory": upload contracts \nand stubs and the project arifact -"Build"->"API Producer": Build successful -"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency -"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Nexus / Artifactory": test asks for [API Producer] stubs -"Nexus / Artifactory"->"SCC Stub Runner": fetch the [API Producer] stubs -"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs -"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub -"SCC Stub Runner"->"API Consumer": communication is correct ----- - -The following code shows an -example using `MockMvc`: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureMockMvc -public class ApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void contextLoads() throws Exception { - mockMvc.perform(get("/resource")) - .andExpect(content().string("Hello World")) - .andDo(document("resource")); - } -} ----- - -This test generates a WireMock stub at "target/snippets/stubs/resource.json". It matches -all GET requests to the "/resource" path. The same example with `WebTestClient` (used -for testing Spring WebFlux applications) would look like this: - -[source,java,indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureWebTestClient -public class ApplicationTests { - - @Autowired - private WebTestClient client; - - @Test - public void contextLoads() throws Exception { - client.get().uri("/resource").exchange() - .expectBody(String.class).isEqualTo("Hello World") - .consumeWith(document("resource")); - } -} ----- - -Without any additional configuration, these tests create a stub with a request matcher -for the HTTP method and all headers except "host" and "content-length". To match the -request more precisely (for example, to match the body of a POST or PUT), we need to -explicitly create a request matcher. Doing so has two effects: - -* Creating a stub that matches only in the way you specify. -* Asserting that the request in the test case also matches the same conditions. - -The main entry point for this feature is `WireMockRestDocs.verify()`, which can be used -as a substitute for the `document()` convenience method, as shown in the following -example: - -[source,java,indent=0] - -import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify; - ----- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureRestDocs(outputDir = "target/snippets") -@AutoConfigureMockMvc -public class ApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - public void contextLoads() throws Exception { - mockMvc.perform(post("/resource") - .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) - .andExpect(status().isOk()) - .andDo(verify().jsonPath("$.id") - .stub("resource")); - } -} ----- - -This contract specifies that any valid POST with an "id" field receives the response -defined in this test. You can chain together calls to `.jsonPath()` to add additional -matchers. If JSON Path is unfamiliar, The https://github.com/jayway/JsonPath[JayWay -documentation] can help you get up to speed. The `WebTestClient` version of this test -has a similar `verify()` static helper that you insert in the same place. - -Instead of the `jsonPath` and `contentType` convenience methods, you can also use the -WireMock APIs to verify that the request matches the created stub, as shown in the -following example: - -[source,java,indent=0] ----- - @Test - public void contextLoads() throws Exception { - mockMvc.perform(post("/resource") - .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) - .andExpect(status().isOk()) - .andDo(verify() - .wiremock(WireMock.post( - urlPathEquals("/resource")) - .withRequestBody(matchingJsonPath("$.id")) - .stub("post-resource")); - } ----- - -The WireMock API is rich. You can match headers, query parameters, and request body by -regex as well as by JSON path. These features can be used to create stubs with a wider -range of parameters. The above example generates a stub resembling the following example: - -.post-resource.json -[source,json] ----- -{ - "request" : { - "url" : "/resource", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$.id" - }] - }, - "response" : { - "status" : 200, - "body" : "Hello World", - "headers" : { - "X-Application-Context" : "application:-1", - "Content-Type" : "text/plain" - } - } -} ----- - -NOTE: You can use either the `wiremock()` method or the `jsonPath()` and `contentType()` -methods to create request matchers, but you can't use both approaches. - -On the consumer side, you can make the `resource.json` generated earlier in this section -available on the classpath (by -<= 20)]', byType()) - } - } -} ----- - -The generated document (formatted in Asciidoc in this case) contains a formatted -contract. The location of this file would be `index/dsl-contract.adoc`. diff --git a/docs/src/main/asciidoc/_verifier_introduction.adoc b/docs/src/main/asciidoc/_verifier_introduction.adoc deleted file mode 100644 index 75bf483d32..0000000000 --- a/docs/src/main/asciidoc/_verifier_introduction.adoc +++ /dev/null @@ -1,120 +0,0 @@ -== Spring Cloud Contract Introduction - -Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. - -=== History - -Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. -It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] -from (https://github.com/Codearte[Codearte]. - -The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. - -=== Why a Contract Verifier? - -Assume that we have a system consisting of multiple microservices: - -image::{github-raw}/docs/src/main/asciidoc/images/Deps.png[Microservices Architecture] - -==== Testing issues - -If we wanted to test the application in top left corner to determine whether it can -communicate with other services, 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. - -*Deploy all microservices and perform end to end tests* - - -Advantages: - -- Simulates production. -- Tests real communication between services. - -Disadvantages: - -- To test one microservice, we have to deploy 6 microservices, a couple of databases, -etc. -- The environment where the tests run is locked for a single suite of tests (nobody else -would be able to run the tests in the meantime). -- They take a long time to run. -- The feedback comes very late in the process. -- They are extremely hard to debug. - -*Mock other microservices in unit/integration tests* - -Advantages: - -- They provide very fast feedback. -- They have no infrastructure requirements. - -Disadvantages: - -- The implementor of the service creates stubs that might have nothing to do with -reality. -- You can go to production with passing tests and failing production. - -To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the -whole world of microservices. If you work on stubs, then the only applications you need -are those that your application directly uses. - -image::{github-raw}/docs/src/main/asciidoc/images/Stubs2.png[Stubbed Services] - -Spring Cloud Contract gives you the certainty that the stubs that you use 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 short, you can trust those stubs. - -=== Purposes - -The main purposes of Spring Cloud Contract are: - -- To ensure that Http / Messaging stubs (used when developing the client) do exactly -what the actual server-side implementation does. -- To promote ATDD (acceptance test driven developement) 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 to be used on the server side. - -By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. - -IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business -features in the contracts. Assume that we have a business use case of fraud check. If a -user can be a fraud for 100 different reasons, we would assume that you would create 2 -contracts, one for the positive case and one for the negative case. Contract tests are -used to test contracts between applications and not to simulate full behavior. - -include::_verifier_how_it_works.adoc[] - -=== Dependencies - -The best way to add dependencies is to use the proper `starter` dependency. - -For `stub-runner`, use `spring-cloud-starter-stub-runner`. When you use a plugin, add -`spring-cloud-starter-contract-verifier`. - -=== Additional Links - -Here are some resources related to Spring Cloud Contract Verifier and Stub Runner. Note -that some may be outdated, because the Spring Cloud Contract Verifier project is under -constant development. - -==== Spring Cloud Contract video - -You can check out the video from the Warsaw JUG about Spring Cloud Contract: - -video::sAAklvxmPmk[youtube,start=538,width=640,height=480] - -==== Readings - -- https://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest] -- https://toomuchcoding.com/blog/categories/accurest/[Accurest related articles from Marcin Grzejszczak's blog] -- https://toomuchcoding.com/blog/categories/spring-cloud-contract/[Spring Cloud Contract related articles from Marcin Grzejszczak's blog] -- https://groovy-lang.org/json.html[Groovy docs regarding JSON] - -=== Samples - -You can find some samples at -https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. diff --git a/docs/src/main/asciidoc/_verifier_messaging.adoc b/docs/src/main/asciidoc/_verifier_messaging.adoc deleted file mode 100644 index 7e3a419100..0000000000 --- a/docs/src/main/asciidoc/_verifier_messaging.adoc +++ /dev/null @@ -1,195 +0,0 @@ -== Spring Cloud Contract Verifier Messaging - -Spring Cloud Contract Verifier lets you verify applications that use messaging as a -means of communication. All of the integrations shown in this document work with Spring, -but you can also create one of your own and use that. - -=== Integrations - -You can use one of the following four integration configurations: - -* Apache Camel -* Spring Integration -* Spring Cloud Stream -* Spring AMQP - -Since we use Spring Boot, if you have added one of these libraries to the classpath, all -the messaging configuration is automatically set up. - -IMPORTANT: Remember to put `@AutoConfigureMessageVerifier` on the base class of your -generated tests. Otherwise, messaging part of Spring Cloud Contract Verifier does not -work. - -IMPORTANT: If you want to use Spring Cloud Stream, remember to add a dependency on -`org.springframework.cloud:spring-cloud-stream-test-support`, as shown here: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - - org.springframework.cloud - spring-cloud-stream-test-support - test - ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -testCompile "org.springframework.cloud:spring-cloud-stream-test-support" ----- - -=== Manual Integration Testing - -The main interface used by the tests is -`org.springframework.cloud.contract.verifier.messaging.MessageVerifier`. -It defines how to send and receive messages. You can create your own implementation to -achieve the same goal. - -In a test, you can inject a `ContractVerifierMessageExchange` to send and receive -messages that follow the contract. Then add `@AutoConfigureMessageVerifier` to your test. -Here's an example: - -[source,java,indent=0] ----- -@RunWith(SpringTestRunner.class) -@SpringBootTest -@AutoConfigureMessageVerifier -public static class MessagingContractTests { - - @Autowired - private MessageVerifier verifier; - ... -} ----- - -NOTE: If your tests require stubs as well, then `@AutoConfigureStubRunner` includes the -messaging configuration, so you only need the one annotation. - -=== Publisher-Side Test Generation - -Having the `input` or `outputMessage` sections in your DSL results in creation of tests -on the publisher's side. By default, JUnit 4 tests are created. However, there is also a -possibility to create JUnit 5, TestNG or Spock tests. - -There are 3 main scenarios that we should take into consideration: - -* Scenario 1: There is no input message that produces an output message. The output -message is triggered by a component inside the application (for example, scheduler). -* Scenario 2: The input message triggers an output message. -* Scenario 3: The input message is consumed and there is no output message. - -IMPORTANT: The destination passed to `messageFrom` or `sentTo` can have different -meanings for different messaging implementations. For *Stream* and *Integration* it is -first resolved as a `destination` of a channel. Then, if there is no such `destination` -it is resolved as a channel name. For *Camel*, that's a certain component (for example, -`jms`). - -==== Scenario 1: No Input Message - -For the given contract: - -.Groovy DSL -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_dsl] ----- - -.YAML -[source,yml,indent=0] ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario1.yml[indent=0] ----- - -The following JUnit test is created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_junit_test] ----- - -And the following Spock test would be created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_test] ----- - -==== Scenario 2: Output Triggered by Input - -For the given contract: - -.Groovy DSL -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_dsl] ----- - -.YAML -[source,yml,indent=0] ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario2.yml[indent=0] ----- - -The following JUnit test is created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_junit] ----- - -And the following Spock test would be created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_spock] ----- - -==== Scenario 3: No Output Message - -For the given contract: - -.Groovy DSL -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] ----- - -.YAML -[source,yml,indent=0] ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] ----- - -The following JUnit test is created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_junit] ----- - -And the following Spock test would be created: - -[source,groovy] ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_spock] ----- - -=== Consumer Stub Generation - -Unlike the HTTP part, in messaging, we need to publish the Groovy DSL inside the JAR with -a stub. Then it is parsed on the consumer side and proper stubbed routes are created. - -For more information, see <> section. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{standalone_messaging_samples_path}/stream-sink/pom.xml[tags=jars,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=jar_setup,indent=0] ----- diff --git a/docs/src/main/asciidoc/_verifier_setup.adoc b/docs/src/main/asciidoc/_verifier_setup.adoc deleted file mode 100644 index e805a2d1c7..0000000000 --- a/docs/src/main/asciidoc/_verifier_setup.adoc +++ /dev/null @@ -1,7 +0,0 @@ -== Spring Cloud Contract Verifier Setup - -You can set up Spring Cloud Contract Verifier in the following ways: - -* link:gradle-project.html[As a Gradle project] -* link:maven-project.html[As a Maven project] -* link:docker-project.html[As a Docker project] \ No newline at end of file diff --git a/docs/src/main/asciidoc/_verifier_stubrunner.adoc b/docs/src/main/asciidoc/_verifier_stubrunner.adoc deleted file mode 100644 index a29dfc2b69..0000000000 --- a/docs/src/main/asciidoc/_verifier_stubrunner.adoc +++ /dev/null @@ -1,182 +0,0 @@ -== Spring Cloud Contract Stub Runner - -One of the issues that you might encounter while using Spring Cloud Contract Verifier is -passing the generated WireMock JSON stubs from the server side to the client side (or to -various clients). The same takes place in terms of client-side generation for messaging. - -Copying the JSON files and setting the client side for messaging manually is out of the -question. That is why we introduced Spring Cloud Contract Stub Runner. It can -automatically download and run the stubs for you. - -=== Snapshot versions - -Add the additional snapshot repository to your `build.gradle` file to use snapshot -versions, which are automatically uploaded after every successful build: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{standalone_samples_path}/http-server/pom.xml[tags=repos,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{standalone_samples_path}/http-server/build.gradle[tags=repos,indent=0] ----- - -[[publishing-stubs-as-jars]] -=== 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. - -TIP: For both Maven and Gradle, the setup comes ready to work. However, you can customize -it if you want to. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - -include::{samples_url}/producer_with_restdocs/pom.xml[tags=skip_jar,indent=0] - - -include::{samples_url}/producer_with_restdocs/pom.xml[tags=assembly,indent=0] - - -include::{samples_url}/producer_with_restdocs/src/assembly/stub.xml[indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=jar_setup,indent=0] ----- - -include::{stubrunner_core_path}/README.adoc[] - -=== Common - -This section briefly describes common properties, including: - -* <> -* <> - -[[common-properties-junit-spring]] -==== Common Properties for JUnit and Spring - -You can set repetitive properties by using system properties or Spring configuration -properties. Here are their names with their default values: - -[frame="topbot",options="header"] -|=============== -| Property name | Default value | Description -|stubrunner.minPort|10000| Minimum value of a port for a started WireMock with stubs. -|stubrunner.maxPort|15000| Maximum value of a port for a started WireMock with stubs. -|stubrunner.repositoryRoot|| Maven repo URL. If blank, then call the local maven repo. -|stubrunner.classifier|stubs| Default classifier for the stub artifacts. -|stubrunner.stubsMode|CLASSPATH| The way you want to fetch and register the stubs -|stubrunner.ids|| Array of Ivy notation stubs to download. -|stubrunner.username|| Optional username to access the tool that stores the JARs with -stubs. -|stubrunner.password|| Optional password to access the tool that stores the JARs with -stubs. -|stubrunner.stubsPerConsumer|false| Set to `true` if you want to use different stubs for -each consumer instead of registering all stubs for every consumer. -|stubrunner.consumerName|| If you want to use a stub for each consumer and want to -override the consumer name just change this value. -|=============== - -[[stub-runner-stub-ids]] -==== Stub Runner Stubs IDs - -You can provide the stubs to download via the `stubrunner.ids` system property. They -follow this pattern: - -[source,java,indent=0] ----- -groupId:artifactId:version:classifier:port ----- - -Note that `version`, `classifier` and `port` are optional. - -* If you do not provide the `port`, a random one will be picked. -* If you do not provide the `classifier`, the default is used. (Note that you can -pass an empty classifier this way: `groupId:artifactId:version:`). -* If you do not provide the `version`, then the `+` will be passed and the latest one is -downloaded. - -`port` means the port of the WireMock server. - -IMPORTANT: Starting with version 1.0.4, you can provide a range of versions that you -would like the Stub Runner to take into consideration. You can read more about the -https://wiki.eclipse.org/Aether/New_and_Noteworthy#Version_Ranges[Aether versioning -ranges here]. - -[[stubrunner-docker]] -=== Stub Runner Docker - -We're publishing a `spring-cloud/spring-cloud-contract-stub-runner` Docker image -that will start the standalone version of Stub Runner. - -If you want to learn more about the basics of Maven, artifact ids, -group ids, classifiers and Artifact Managers, just click here <>. - -==== How to use it - -Just execute the docker image. You can pass any of the <> -as environment variables. The convention is that all the -letters should be upper case. The camel case notation should -and the dot (`.`) should be separated via underscore (`_`). E.g. - the `stubrunner.repositoryRoot` property should be represented - as a `STUBRUNNER_REPOSITORY_ROOT` environment variable. - -==== Example of client side usage in a non JVM project - -We'd like to use the stubs created in this <> step. -Let's assume that we want to run the stubs on port `9876`. The NodeJS code -is available here: - -```bash -$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs -$ cd bookstore -``` - -Let's run the Stub Runner Boot application with the stubs. - -```bash -# Provide the Spring Cloud Contract Docker version -$ SC_CONTRACT_DOCKER_VERSION="..." -# The IP at which the app is running and Docker container can reach it -$ APP_IP="192.168.0.100" -# Spring Cloud Contract Stub Runner properties -$ STUBRUNNER_PORT="8083" -# Stub coordinates 'groupId:artifactId:version:classifier:port' -$ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876" -$ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local" -# Run the docker with Stub Runner Boot -$ docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" -e "STUBRUNNER_STUBS_MODE=REMOTE" -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" -p "9876:9876" springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}" -``` - -What's happening is that - -- a standalone Stub Runner application got started -- it downloaded the stub with coordinates `com.example:bookstore:0.0.1.RELEASE:stubs` on port `9876` -- it got downloaded from Artifactory running at `http://192.168.0.100:8081/artifactory/libs-release-local` -- after a while Stub Runner will be running on port `8083` -- and the stubs will be running at port `9876` - -On the server side we built a stateful stub. Let's use curl to assert -that the stubs are setup properly. - -```bash -# let's execute the first request (no response is returned) -$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books -# Now time for the second request -$ curl -X GET http://localhost:9876/api/books -# You will receive contents of the JSON -``` - -IMPORTANT: If you want use the stubs that you have built locally, on your host, -then you should pass the environment variable `-e STUBRUNNER_STUBS_MODE=LOCAL` and mount -the volume of your local m2 `-v "${HOME}/.m2/:/root/.m2:ro"` \ No newline at end of file diff --git a/docs/src/main/asciidoc/_verifier_stubrunner_msg.adoc b/docs/src/main/asciidoc/_verifier_stubrunner_msg.adoc deleted file mode 100644 index 1a5a68a96f..0000000000 --- a/docs/src/main/asciidoc/_verifier_stubrunner_msg.adoc +++ /dev/null @@ -1,75 +0,0 @@ -[[stub-runner-for-messaging]] -== Stub Runner for Messaging - -Stub Runner can run the published stubs in memory. It can integrate with the following -frameworks: - -* Spring Integration -* Spring Cloud Stream -* Apache Camel -* Spring AMQP - -It also provides entry points to integrate with any other solution on the market. - -IMPORTANT: If you have multiple frameworks on the classpath Stub Runner will need to -define which one should be used. Let's assume that you have both AMQP, Spring Cloud Stream and Spring Integration -on the classpath. Then you need to set `stubrunner.stream.enabled=false` and `stubrunner.integration.enabled=false`. -That way the only remaining framework is Spring AMQP. - -=== Stub triggering - -To trigger a message, use the `StubTrigger` interface: - -[source,groovy] ----- -include::{stubrunner_core_path}/src/main/java/org/springframework/cloud/contract/stubrunner/StubTrigger.java[lines=16..-1] ----- - -For convenience, the `StubFinder` interface extends `StubTrigger`, so you only need one -or the other in your tests. - -`StubTrigger` gives you the following options to trigger a message: - -* <> -* <> -* <> -* <> - -[[trigger-label]] -==== Trigger by Label - -[source,groovy] ----- -include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger,indent=0] ----- -[[trigger-group-artifact-ids]] -==== Trigger by Group and Artifact Ids - -[source,groovy] ----- -include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_group_artifact,indent=0] ----- - -[[trigger-artifact-ids]] -==== Trigger by Artifact Ids - -[source,groovy] ----- -include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_artifact,indent=0] ----- - -[[trigger-all-messages]] -==== Trigger All Messages - -[source,groovy] ----- -include::{tests_path}/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_all,indent=0] ----- - -include::{tests_path}/spring-cloud-contract-stub-runner-camel/README.adoc[] - -include::{tests_path}/spring-cloud-contract-stub-runner-integration/README.adoc[] - -include::{tests_path}/spring-cloud-contract-stub-runner-stream/README.adoc[] - -include::{tests_path}/spring-cloud-contract-stub-runner-amqp/README.adoc[] diff --git a/docs/src/main/asciidoc/advanced.adoc b/docs/src/main/asciidoc/advanced.adoc new file mode 100644 index 0000000000..805a1e63c7 --- /dev/null +++ b/docs/src/main/asciidoc/advanced.adoc @@ -0,0 +1,373 @@ +[[contract-customization]] += Spring Cloud Contract customization +include::_attributes.adoc[] + +In this section we describe how to customize various parts of Spring Cloud Contract. + +[[customization-customization]] +== DSL Customization + +IMPORTANT: This section is valid only for Groovy DSL + +You can customize the Spring Cloud Contract Verifier by extending the DSL, as shown in +the remainder of this section. + +[[customization-extending]] +=== Extending the DSL + +You can provide your own functions to the DSL. The key requirement for this feature is to +maintain the static compatibility. Later in this document, you can see examples of: + +* Creating a JAR with reusable classes. +* Referencing of these classes in the DSLs. + +You can find the full example +https://github.com/spring-cloud-samples/spring-cloud-contract-samples[here]. + +[[customization-extending-common-jar]] +=== Common JAR + +The following examples show three classes that can be reused in the DSLs. + +*PatternUtils* contains functions used by both the **consumer** and the **producer**. + +[source,java] +---- +include::{samples_url}/common/src/main/java/com/example/PatternUtils.java[] +---- + +*ConsumerUtils* contains functions used by the **consumer**. + +[source,java] +---- +include::{samples_url}/common/src/main/java/com/example/ConsumerUtils.java[] +---- + +*ProducerUtils* contains functions used by the **producer**. + +[source,java] +---- +include::{samples_url}/common/src/main/java/com/example/ProducerUtils.java[] +---- + +[[customization-test-dep]] +=== Add a test dependency in the Project's Dependencies + +First, add the common jar dependency as a test dependency. Because your contracts files +are available on the test resources path, the common jar classes automatically become +visible in your Groovy files. The following examples show how to test the dependency: + +== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{samples_url}/producer/pom.xml[tags=test_dep,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{samples_url}/producer/build.gradle[tags=test_dep,indent=0] +---- +== + +[[customization-plugin-dep]] +=== Add a test dependency in the Plugin's Dependencies + +Now, you must add the dependency for the plugin to reuse at runtime, as shown in the +following example: + +== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{samples_url}/producer/pom.xml[tags=test_dep_in_plugin,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{samples_url}/producer/build.gradle[tags=test_dep_in_plugin,indent=0] +---- +== + +[[customization-referencing]] +=== Referencing classes in DSLs + +You can now reference your classes in your DSL, as shown in the following example: + +[source,groovy] +---- +include::{samples_url}/producer/src/test/resources/contracts/beer/rest/shouldGrantABeerIfOldEnough.groovy[indent=0] +---- + +IMPORTANT: You can set the Spring Cloud Contract plugin up by setting `convertToYaml` to `true`. That way you will NOT have to add the dependency with the extended functionality to the consumer side, since the consumer side will be using YAML contracts instead of Groovy ones. + +[[customization-wiremock]] +== WireMock customization + +In this section you will learn how to customize the way you work with https://wiremock.org[WireMock]. + +[[customization-wiremock-extension]] +=== Registering Your Own WireMock Extension + +WireMock lets you register custom extensions. By default, Spring Cloud Contract registers +the transformer, which lets you reference a request from a response. If you want to +provide your own extensions, you can register an implementation of the +`org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions` interface. +Since we use the spring.factories extension approach, you can create an entry in +`META-INF/spring.factories` file similar to the following: + +[source,groovy,indent=0] +---- +include::{stubrunner_core_path}/src/test/resources/META-INF/spring.factories[indent=0] +---- + +The following is an example of a custom extension: + +.TestWireMockExtensions.groovy +[source,groovy,indent=0] +---- +include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/TestWireMockExtensions.groovy[indent=0] +---- + +IMPORTANT: Remember to override the `applyGlobally()` method and set it to `false` if you +want the transformation to be applied only for a mapping that explicitly requires it. + +[[customization-wiremock-configuration]] +=== Customization of WireMock configuration + +You can register a bean of `org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer` type +in order to customize the WireMock configuration (e.g. add custom transformers). +Example: + +[source,java,indent=0] +---- +include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_1] +// perform your customization here +include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockConfigurationCustomizerTests.java[tags=customizer_2] +---- + +[[customization-pluggable-architecture]] +== Using the Pluggable Architecture + +You may encounter cases where you have your contracts have been defined in other formats, +such as YAML, RAML or PACT. In those cases, you still want to benefit from the automatic +generation of tests and stubs. You can add your own implementation for generating both +tests and stubs. Also, you can customize the way tests are generated (for example, you +can generate tests for other languages) and the way stubs are generated (for example, you +can generate stubs for other HTTP server implementations). + +[[customization-custom-contract-converter]] +=== Custom Contract Converter + +The `ContractConverter` interface lets you register your own implementation of a contract +structure converter. The following code listing shows the `ContractConverter` interface: + +[source,java] +---- +include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/ContractConverter.java[indent=0,lines=17..-1] +---- + +Your implementation must define the condition on which it should start the +conversion. Also, you must define how to perform that conversion in both directions. + +IMPORTANT: Once you create your implementation, you must create a +`/META-INF/spring.factories` file in which you provide the fully qualified name of your +implementation. + +The following example shows a typical `spring.factories` file: + +[source] +---- +org.springframework.cloud.contract.spec.ContractConverter=\ +org.springframework.cloud.contract.verifier.converter.YamlContractConverter +---- + +[[customization-custom-test-generator]] +=== Using the Custom Test Generator + +If you want to generate tests for languages other than Java or you are not happy with the +way the verifier builds Java tests, you can register your own implementation. + +The `SingleTestGenerator` interface lets you register your own implementation. The +following code listing shows the `SingleTestGenerator` interface: + +[source,groovy] +---- +include::{verifier_core_path}/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java[indent=0,lines=17..-1] +---- + +Again, you must provide a `spring.factories` file, such as the one shown in the following +example: + +[source] +---- +org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/ +com.example.MyGenerator +---- + +[[customization-custom-stub-generator]] +=== Using the Custom Stub Generator + +If you want to generate stubs for stub servers other than WireMock, you can plug in your +own implementation of the `StubGenerator` interface. The following code listing shows the +`StubGenerator` interface: + +[source,groovy] +---- +include::{converters_path}/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGenerator.groovy[indent=0,lines=16..-1] +---- + +Again, you must provide a `spring.factories` file, such as the one shown in the following +example: + +[source] +---- +include::{converters_path}/src/main/resources/META-INF/spring.factories[indent=0] +---- + +The default implementation is the WireMock stub generation. + +TIP: You can provide multiple stub generator implementations. For example, from a single +DSL, you can produce both WireMock stubs and Pact files. + +[[customization-custom-stub-runner]] +=== Using the Custom Stub Runner + +If you decide to use a custom stub generation, you also need a custom way of running +stubs with your different stub provider. + +Assume that you use https://github.com/dreamhead/moco[Moco] to build your stubs and that +you have written a stub generator and placed your stubs in a JAR file. + +In order for Stub Runner to know how to run your stubs, you have to define a custom +HTTP Stub server implementation, which might resemble the following example: + +[source,groovy] +---- +include::{tests_path}/spring-cloud-contract-stub-runner-moco/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/moco/MocoHttpServerStub.groovy[indent=0,lines=16..-1] +---- + +Then, you can register it in your `spring.factories` file, as shown in the following +example: + +[source] +---- +org.springframework.cloud.contract.stubrunner.HttpServerStub=\ +org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub +---- + +Now you can run stubs with Moco. + +IMPORTANT: If you do not provide any implementation, then the default (WireMock) +implementation is used. If you provide more than one, the first one on the list is used. + +[[customization-custom-stub-downloader]] +=== Using the Custom Stub Downloader + +You can customize the way your stubs are downloaded by creating an implementation of the +`StubDownloaderBuilder` interface, as shown in the following example: + +[source,java] +---- +package com.example; + +class CustomStubDownloaderBuilder implements StubDownloaderBuilder { + + @Override + public StubDownloader build(final StubRunnerOptions stubRunnerOptions) { + return new StubDownloader() { + @Override + public Map.Entry downloadAndUnpackStubJar( + StubConfiguration config) { + File unpackedStubs = retrieveStubs(); + return new AbstractMap.SimpleEntry<>( + new StubConfiguration(config.getGroupId(), config.getArtifactId(), version, + config.getClassifier()), unpackedStubs); + } + + File retrieveStubs() { + // here goes your custom logic to provide a folder where all the stubs reside + } +} +---- + +Then you can register it in your `spring.factories` file, as shown in the following +example: + +[source] +---- +# Example of a custom Stub Downloader Provider +org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\ +com.example.CustomStubDownloaderBuilder +---- + +Now you can pick a folder with the source of your stubs. + +IMPORTANT: If you do not provide any implementation, then the default is used (scan classpath). +If you provide the `stubsMode = StubRunnerProperties.StubsMode.LOCAL` or +`, stubsMode = StubRunnerProperties.StubsMode.REMOTE` then the Aether implementation will be used +If you provide more than one, then the first one on the list is used. + +[[scm-stub-downloader]] +=== Using the SCM Stub Downloader + +Whenever the `repositoryRoot` starts with a SCM protocol +(currently we support only `git://`), the stub downloader will try +to clone the repository and use it as a source of contracts +to generate tests or stubs. + +Either via environment variables, system properties, properties set +inside the plugin or contracts repository configuration you can +tweak the downloader's behaviour. Below you can find the list of +properties + +.SCM Stub Downloader properties +|==== +|Type of a property |Name of the property | Description +| +* `git.branch` (plugin prop) + +* `stubrunner.properties.git.branch` (system prop) + +* `STUBRUNNER_PROPERTIES_GIT_BRANCH` (env prop) +|master +|Which branch to checkout + +| +* `git.username` (plugin prop) + +* `stubrunner.properties.git.username` (system prop) + +* `STUBRUNNER_PROPERTIES_GIT_USERNAME` (env prop) +| +|Git clone username + +| +* `git.password` (plugin prop) + +* `stubrunner.properties.git.password` (system prop) + +* `STUBRUNNER_PROPERTIES_GIT_PASSWORD` (env prop) +| +|Git clone password + +| +* `git.no-of-attempts` (plugin prop) + +* `stubrunner.properties.git.no-of-attempts` (system prop) + +* `STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS` (env prop) +|10 +|Number of attempts to push the commits to `origin` + +| +* `git.wait-between-attempts` (Plugin prop) + +* `stubrunner.properties.git.wait-between-attempts` (system prop) + +* `STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS` (env prop) +|1000 +|Number of millis to wait between attempts to push the commits to `origin` +|==== diff --git a/docs/src/main/asciidoc/docker-project.adoc b/docs/src/main/asciidoc/docker-project.adoc index 2547579835..67185d90f1 100644 --- a/docs/src/main/asciidoc/docker-project.adoc +++ b/docs/src/main/asciidoc/docker-project.adoc @@ -1,3 +1,4 @@ +[[docker]] = Docker Project include::_attributes.adoc[] @@ -8,6 +9,10 @@ against a running application. TIP: The `EXPLICIT` mode means that the tests generated from contracts will send real requests and not the mocked ones. +We're also publishing a `spring-cloud/spring-cloud-contract-stub-runner` Docker image +that will start the standalone version of Stub Runner. + +[[docker-intro]] == Short intro to Maven, JARs and Binary storage Since the Docker image can be used by non JVM projects, it's good to @@ -48,7 +53,8 @@ and for Docker those would be Docker images. You can store those artifacts in a manager. Examples of such managers can be https://jfrog.com/artifactory/[Artifactory] or https://www.sonatype.org/nexus/[Nexus]. -== How it works +[[docker-how-it-works]] +== Generating tests on the producer side The image searches for contracts under the `/contracts` folder. The output from running the tests will be available under @@ -63,6 +69,7 @@ It's enough for you to mount your contracts, pass the environment variables - generate the https://github.com/tomakehurst/wiremock[WireMock] stubs - (optional - turned on by default) publish the stubs to a Artifact Manager +[[docker-env-vars]] === Environment Variables The Docker image requires some environment variables to point to @@ -94,12 +101,11 @@ requires authentication, defaults to `REPO_WITH_BINARIES_USERNAME`. If that's no requires authentication, defaults to `REPO_WITH_BINARIES_PASSWORD`. If that's not set defaults to `password`. - `EXTERNAL_CONTRACTS_PATH` - path to contracts for the given project, inside the project with contracts. Defaults to slash separated `EXTERNAL_CONTRACTS_GROUP_ID` concatenated with `/` and `EXTERNAL_CONTRACTS_ARTIFACT_ID`. E.g. -for group id `foo.bar` and artifact id `baz`, would result in `foo/bar/baz` contracts path. +for group id `foo-server-side.bar` and artifact id `baz`, would result in `foo/bar/baz` contracts path. - `EXTERNAL_CONTRACTS_WORK_OFFLINE` - if set to `true` then will retrieve artifact with contracts from the container's `.m2`. Mount your local `.m2` as a volume available at the container's `/root/.m2` path. You must not set both `EXTERNAL_CONTRACTS_WORK_OFFLINE` and `EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL`. - These environment variables are used when tests are executed: - `APPLICATION_BASE_URL` - url against which tests should be executed. @@ -108,7 +114,8 @@ will not work) - `APPLICATION_USERNAME` - (optional) username for basic authentication to your application - `APPLICATION_PASSWORD` - (optional) password for basic authentication to your application -== Example of usage +[[docker-example-of-usage]] +=== Example of usage Let's take a look at a simple MVC application @@ -119,9 +126,6 @@ $ cd bookstore The contracts are available under `/contracts` folder. -[[docker-server-side]] -== Server side (nodejs) - Since we want to run tests, we could just execute: ```bash @@ -178,5 +182,72 @@ will be executed against the running application under http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/ . The stubs will be here http://localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/bookstore-0.0.1.RELEASE-stubs.jar. -// TODO: Fix the link -To see how the client side looks like check out the <> section. +[[docker-stubrunner]] +== Running stubs on the consumer side + +This section describes hot to use Docker on the consumer side to fetch and run stubs. + +We're publishing a `spring-cloud/spring-cloud-contract-stub-runner` Docker image +that will start the standalone version of Stub Runner. + +[[docker-stubrunner-env-vars]] +=== Environment variables + +//TODO: Fix the link +Just execute the docker image. You can pass any of the <> +as environment variables. The convention is that all the +letters should be upper case. The camel case notation should +and the dot (`.`) should be separated via underscore (`_`). E.g. + the `stubrunner.repositoryRoot` property should be represented + as a `STUBRUNNER_REPOSITORY_ROOT` environment variable. + +[[docker-stubrunner-example]] +=== Example of usage + +We'd like to use the stubs created in this <> step. +Let's assume that we want to run the stubs on port `9876`. The NodeJS code +is available here: + +```bash +$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs +$ cd bookstore +``` + +Let's run the Stub Runner Boot application with the stubs. + +```bash +# Provide the Spring Cloud Contract Docker version +$ SC_CONTRACT_DOCKER_VERSION="..." +# The IP at which the app is running and Docker container can reach it +$ APP_IP="192.168.0.100" +# Spring Cloud Contract Stub Runner properties +$ STUBRUNNER_PORT="8083" +# Stub coordinates 'groupId:artifactId:version:classifier:port' +$ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876" +$ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local" +# Run the docker with Stub Runner Boot +$ docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" -e "STUBRUNNER_STUBS_MODE=REMOTE" -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" -p "9876:9876" springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}" +``` + +What's happening is that + +- a standalone Stub Runner application got started +- it downloaded the stub with coordinates `com.example:bookstore:0.0.1.RELEASE:stubs` on port `9876` +- it got downloaded from Artifactory running at `http://192.168.0.100:8081/artifactory/libs-release-local` +- after a while Stub Runner will be running on port `8083` +- and the stubs will be running at port `9876` + +On the server side we built a stateful stub. Let's use curl to assert +that the stubs are setup properly. + +```bash +# let's execute the first request (no response is returned) +$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books +# Now time for the second request +$ curl -X GET http://localhost:9876/api/books +# You will receive contents of the JSON +``` + +IMPORTANT: If you want use the stubs that you have built locally, on your host, +then you should pass the environment variable `-e STUBRUNNER_STUBS_MODE=LOCAL` and mount +the volume of your local m2 `-v "${HOME}/.m2/:/root/.m2:ro"` diff --git a/docs/src/main/asciidoc/documentation-overview.adoc b/docs/src/main/asciidoc/documentation-overview.adoc index 05741c891d..d22f628f67 100644 --- a/docs/src/main/asciidoc/documentation-overview.adoc +++ b/docs/src/main/asciidoc/documentation-overview.adoc @@ -81,9 +81,7 @@ Need more details about {project-full-name}'s core features? * *Core Features:* <<{project-name}-features.adoc#contract-features-spring-application, SpringApplication>> | -<<{project-name}-features.adoc#contract-features-external-config, External Configuration>> | -<<{project-name}-features.adoc#contract-features-profiles, Profiles>> | -<<{project-name}-features.adoc#contract-features-logging, Logging>> +<<{project-name}-features.adoc#contract-features-external-config, External Configuration>> * *Build tools:* link:maven-project.html[Maven] | link:gradle-project.html[Gradle] | @@ -94,12 +92,22 @@ link:docker-project.html[Docker] == Advanced Topics Finally, we have a few topics for more advanced users: -* *{project-full-name} Applications Deployment:* -<> | -<> -* *Build tool plugins:* -<> | -<> +* *Customizing DSL:* +<> | +<> | +<> | +<> | +<> +* *Customizing WireMock:* +<> | +<> +* *Customizing {project-full-name}:* +<> | +<> | +<> | +<> | +<> | +<> * *Appendix:* <> | <> | diff --git a/docs/src/main/asciidoc/getting-started.adoc b/docs/src/main/asciidoc/getting-started.adoc index 3176d9aa4d..6b369a278f 100644 --- a/docs/src/main/asciidoc/getting-started.adoc +++ b/docs/src/main/asciidoc/getting-started.adoc @@ -1,1139 +1,4 @@ [[getting-started]] = Getting Started include::_attributes.adoc[] - -If you are getting started with {project-full-name}, or "`Spring`" in general, start by reading -this section. It answers the basic "`what?`", "`how?`" and "`why?`" questions. It -includes an introduction to {project-full-name}, along with installation instructions. We then -walk you through building your first {project-full-name} application, discussing some core -principles as we go. - -[[getting-started-introducing-spring-cloud-contract]] -== Introducing Spring Cloud Contract - -Spring Cloud Contract moves TDD to the level of software architecture. It allows to perform consumer, consumer-driven and producer-driven contract testing. - -[[getting-started-introducing-spring-cloud-contract-history]] -=== History - -Before becoming Spring Cloud Contract, this project was called https://github.com/Codearte/accurest[Accurest]. -It was created by https://twitter.com/mgrzejszczak[Marcin Grzejszczak] and https://twitter.com/jkubrynski[Jakub Kubrynski] -from (https://github.com/Codearte[Codearte]). - -The `0.1.0` release took place on 26 Jan 2015 and it became stable with `1.0.0` release on 29 Feb 2016. - -[[getting-started-introducing-spring-cloud-contract-why]] -==== Why would you need it? - -Assume that we have a system consisting of multiple microservices: - -image::images/Deps.png[Microservices Architecture] - -[[getting-started-introducing-spring-cloud-contract-testing-issues]] -==== Testing issues - -If we wanted to test the application in top left corner to determine whether it can -communicate with other services, 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. - -*Deploy all microservices and perform end to end tests* - -Advantages: - -- Simulates production. -- Tests real communication between services. - -Disadvantages: - -- To test one microservice, we have to deploy 6 microservices, a couple of databases, -etc. -- The environment where the tests run is locked for a single suite of tests (nobody else -would be able to run the tests in the meantime). -- They take a long time to run. -- The feedback comes very late in the process. -- They are extremely hard to debug. - -*Mock other microservices in unit/integration tests* - -Advantages: - -- They provide very fast feedback. -- They have no infrastructure requirements. - -Disadvantages: - -- The implementor of the service creates stubs that might have nothing to do with -reality. -- You can go to production with passing tests and failing production. - -To solve the aforementioned issues, Spring Cloud Contract was created. The main idea is to give you very fast feedback, without the need to set up the -whole world of microservices. If you work on stubs, then the only applications you need -are those that your application directly uses. - -image::images/Stubs2.png[Stubbed Services] - -Spring Cloud Contract gives you the certainty that the stubs that you use 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 short, you can trust those stubs. - -[[getting-started-introducing-spring-cloud-contract-purposes]] -=== Purposes - -The main purposes of Spring Cloud Contract are: - -- To ensure that HTTP / Messaging stubs (used when developing the client) do exactly -what the actual server-side implementation does. -- To promote ATDD (acceptance test driven developement) 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 to be used on the server side. - -By default Spring Cloud Contract integrates with http://wiremock.org[Wiremock] as the HTTP server stub. - -IMPORTANT: Spring Cloud Contract's purpose is NOT to start writing business -features in the contracts. Assume that we have a business use case of fraud check. If a -user can be a fraud for 100 different reasons, we would assume that you would create 2 -contracts, one for the positive case and one for the negative case. Contract tests are -used to test contracts between applications and not to simulate full behavior. - -[[getting-started-what-is-a-contract]] -=== What is a contract? - -As consumers of services, we need to define what exactly we want to achieve. We need to -formulate our expectations. That is why we write contracts. In other words a contract is an agreement on how the API / message communication should look like. Let's look at the following example - -Assume that you want to send a request containing the ID of a client company and the -amount it wants to borrow from us. You also want to send it to the /fraudcheck url via -the PUT method. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -[[getting-started-three-second-tour]] -== A Three-second Tour - -This very brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find a somewhat longer tour -<>. - -[plantuml, getting-started-three-second, png] ----- -"API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin -"API Producer"->"API Producer": add SCC Verifier dependency -"API Producer"->"API Producer": define contracts -"API Producer"->"Build": run build -"Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) -"Build"->"Stub Storage": upload contracts \nand stubs and the project arifact -"Build"->"API Producer": Build successful -"API Consumer"->"API Consumer": add SCC Stub Runner \ndependency -"API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test -"SCC Stub Runner"->"Stub Storage": test asks for [API Producer] stubs -"Stub Storage"->"SCC Stub Runner": fetch the [API Producer] stubs -"SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs -"API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub -"SCC Stub Runner"->"API Consumer": communication is correct ----- - -[[getting-started-three-second-tour-producer]] -=== On the Producer Side - -To start working with Spring Cloud Contract, add files with `REST`/messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -Then add the Spring Cloud Contract Verifier dependency and plugin to your build file, as -shown in the following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the tests get generated under -`org.springframework.cloud.contract.verifier.tests.`. - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of either handling HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests, and it -should contain all the setup necessary to run them (for example `RestAssuredMockMvc` -controller setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -The changes can now be merged, and both the application and the stub artifacts may be -published in an online repository. - -[[getting-started-three-second-tour-consumer]] -=== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To do so, add the dependency to `Spring Cloud Contract Stub Runner`, as shown in the -following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the stubs -by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -TIP: The tests are being skipped because the Producer-side contract implementation is not -in place yet, so the automatically-generated contract tests fail. -* By getting already-existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URL as `Spring Cloud Contract -Stub Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` values for `Spring Cloud Contract Stub Runner` to -run the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -Now, in your integration test, you can receive stubbed versions of HTTP responses or -messages that are expected to be emitted by the collaborator service. - -[[getting-started-first-application]] -== Developing Your First Spring Cloud Contract based application - -This brief tour walks through using Spring Cloud Contract: - -* <> -* <> - -You can find an even more brief tour -<>. - -For the sake of this example the `Stub Storage` will be Nexus / Artifactory. - -image::getting-started-three-second.png[Getting started first application] - -[[getting-started-first-application-producer]] -=== On the Producer Side - -To start working with `Spring Cloud Contract`, add Spring Cloud Contract Verifier dependency and plugin to your build file, -as shown in the following example: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -The following listing shows how to add the plugin, which should go in the build/plugins -portion of the file: - -[source,xml,indent=0] ----- - - org.springframework.cloud - spring-cloud-contract-maven-plugin - ${spring-cloud-contract.version} - true - ----- - -Now let's add files with `REST/` messaging contracts -expressed in either Groovy DSL or YAML to the contracts directory, which is set by the -`contractsDslDir` property. By default, it is `$rootDir/src/test/resources/contracts`. - -For the HTTP stubs, a contract defines what kind of response should be returned for a -given request (taking into account the HTTP methods, URLs, headers, status codes, and so -on). The following example shows how an HTTP stub contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -package contracts - -org.springframework.cloud.contract.spec.Contract.make { - request { - method 'PUT' - url '/fraudcheck' - body([ - "client.id": $(regex('[0-9]{10}')), - loanAmount: 99999 - ]) - headers { - contentType('application/json') - } - } - response { - status OK() - body([ - fraudCheckStatus: "FRAUD", - "rejection.reason": "Amount too high" - ]) - headers { - contentType('application/json') - } - } -} ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -request: - method: PUT - url: /fraudcheck - body: - "client.id": 1234567890 - loanAmount: 99999 - headers: - Content-Type: application/json - matchers: - body: - - path: $.['client.id'] - type: by_regex - value: "[0-9]{10}" -response: - status: 200 - body: - fraudCheckStatus: "FRAUD" - "rejection.reason": "Amount too high" - headers: - Content-Type: application/json;charset=UTF-8 ----- -==== - -In the case of messaging, you can define: - -* The input and the output messages can be defined (taking into account from and where it -was sent, the message body, and the header). -* The methods that should be called after the message is received. -* The methods that, when called, should trigger a message. - -The following example shows a Camel messaging contract: - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{verifier_core_path}/src/test/resources/yml/contract_message_scenario3.yml[indent=0] ----- -==== - -Running `./mvnw clean install` automatically generates tests that verify the application -compliance with the added contracts. By default, the generated tests are under -`org.springframework.cloud.contract.verifier.tests.`. - -The generated tests will differ, depending on which framework and test type you've setup in your plugin. - -In the samples below you'll find: - -- the default test mode for HTTP contracts via `MockMvc` -- JAX-RS client via the `JAXRS` test mode -- `WebTestClient` based test (this is particularly recommended while working with Reactive, `Web-Flux`-based applications) set via `WEBTESTCLIENT` test mode -- Spock based test via the `testFramework` property set to `SPOCK` - -==== -[source,java,indent=0,role="primary"] -.mockmvc ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -[source,java,indent=0,role="secondary"] -.jaxrs ----- -@SuppressWarnings("rawtypes") -public class FooTest { - WebTarget webTarget; - - @Test - public void validate_() throws Exception { - - // 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() - .build("GET") - .invoke(); - String responseAsString = response.readEntity(String.class); - - // then: - assertThat(response.getStatus()).isEqualTo(200); - - // and: - DocumentContext parsedJson = JsonPath.parse(responseAsString); - assertThatJson(parsedJson).field("['property1']").isEqualTo("a"); - } - -} ----- - -[source,java,indent=0,role="secondary"] -.webtestclient ----- -@Test - public void validate_shouldRejectABeerIfTooYoung() throws Exception { - // given: - WebTestClientRequestSpecification request = given() - .header("Content-Type", "application/json") - .body("{\"age\":10}"); - - // when: - WebTestClientResponse response = given().spec(request) - .post("/check"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK"); - } ----- - -[source,groovy,indent=0,role="secondary"] -.spock ----- -given: - ContractVerifierMessage inputMessage = contractVerifierMessaging.create( - \'\'\'{"bookName":"foo"}\'\'\', - ['sample': 'header'] - ) - -when: - contractVerifierMessaging.send(inputMessage, 'jms:delete') - -then: - noExceptionThrown() - bookWasDeleted() ----- -==== - -As the implementation of the functionalities described by the contracts is not yet -present, the tests fail. - -To make them pass, you must add the correct implementation of handling either HTTP -requests or messages. Also, you must add a correct base test class for auto-generated -tests to the project. This class is extended by all the auto-generated tests and should -contain all the setup necessary to run them (for example, `RestAssuredMockMvc` controller -setup or messaging test setup). - -Once the implementation and the test base class are in place, the tests pass, and both the -application and the stub artifacts are built and installed in the local Maven repository. -Information about installing the stubs jar to the local repository appears in the logs, as -shown in the following example: - -[source,bash,indent=0] ----- - [INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar - [INFO] - [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- - [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar - [INFO] - [INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- - [INFO] - [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar - [INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom - [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -You can now merge the changes and publish both the application and the stub artifacts -in an online repository. - -[[getting-started-first-application-consumer]] -=== On the Consumer Side - -`Spring Cloud Contract Stub Runner` can be used in the integration tests to get a running -WireMock instance or messaging route that simulates the actual service. - -To get started, add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -You can get the Producer-side stubs installed in your Maven repository in either of two -ways: - -* By checking out the Producer side repository and adding contracts and generating the -stubs by running the following commands: -+ -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- -NOTE: The tests are skipped because the Producer-side contract implementation is not yet -in place, so the automatically-generated contract tests fail. -* Getting already existing producer service stubs from a remote repository. To do so, -pass the stub artifact IDs and artifact repository URl as `Spring Cloud Contract Stub -Runner` properties, as shown in the following example: -+ -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -Now you can annotate your test class with `@AutoConfigureStubRunner`. In the annotation, -provide the `group-id` and `artifact-id` for `Spring Cloud Contract Stub Runner` to run -the collaborators' stubs for you, as shown in the following example: - -[source,java, indent=0] ----- -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment=WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) -public class LoanApplicationServiceTests { ----- - -TIP: Use the `REMOTE` `stubsMode` when downloading stubs from an online repository and -`LOCAL` for offline work. - -In your integration test, you can receive stubbed versions of HTTP responses or messages -that are expected to be emitted by the collaborator service. You can see entries similar -to the following in the build logs: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -[[getting-started-cdc]] -== Step-by-step Guide to Consumer Driven Contracts (CDC) with contracts laying on the producer side - -Consider an example of Fraud Detection and the Loan Issuance process. The business -scenario is such that we want to issue loans to people but do not want them to steal from -us. The current implementation of our system grants loans to everybody. - -Assume that `Loan Issuance` is a client to the `Fraud Detection` server. In the current -sprint, we must develop a new feature: if a client wants to borrow too much money, then -we mark the client as a fraud. - -Technical remarks - -* Fraud Detection has an `artifact-id` of `http-server`, -* Loan -Issuance has an artifact-id of `http-client` -* both have a `group-id` of `com.example` -* For the sake of this example the `Stub Storage` will be Nexus / Artifactory - -Social remarks - -* both client and server development teams need to communicate directly and -discuss changes while going through the process -* CDC is all about communication. - -The https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-server[server -side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/{branch}/samples/standalone/dsl/http-client[the -client code here]. - -TIP: In this case, the producer owns the contracts. Physically, all the contract are -in the producer's repository. - -[[getting-started-cdc-technical-note]] -=== Technical note - -If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the -following section to your build: - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repos,indent=0] ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0] ----- - -For simplicity we will use the following acronyms: - -- Loan Issuance - LI - the http client -- Fraud Detection - FD - the http server -- Spring Cloud Contract - SCC - -=== Consumer side (Loan Issuance) - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps: - -. Start doing TDD by writing a test for your feature. -. Write the missing implementation. -. Clone the Fraud Detection service repository locally. -. Define the contract locally in the repo of Fraud Detection service. -. Add the Spring Cloud Contract (SCC) plugin. -. Run the integration tests. -. File a pull request. -. Create an initial implementation. -. Take over the pull request. -. Write the missing implementation. -. Deploy your app. -. Work online. - -Let's start with the Loan Issuance flow: - -[plantuml, getting-started-cdc-client, png] ----- -"Loan\nIssuance"->"Loan\nIssuance": start doing TDD\nby writing a test\nfor your feature -"Loan\nIssuance"->"Loan\nIssuance": write the \nmissing implementation -"Loan\nIssuance"->"Loan\nIssuance": run a test - it fails\ndue to no server running -"Loan\nIssuance"->"Fraud\nDetection\nClone": clone the repository -"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add missing dependencies\n& define contracts -"Fraud\nDetection\nClone"->"Fraud\nDetection\nClone": add the SCC plugin -"Fraud\nDetection\nClone"->"FD \nClone Build": install the stubs locally -"FD \nClone Build"->"SCC Plugin \nin FD Clone": generate stubs \nand stubs \nartifact (e.g. stubs-jar) -"SCC Plugin \nin FD Clone"->"FD \nClone Build": stubs and artifacts\ngenerated -"FD \nClone Build"->"Local storage": install the stubs locally -"Local storage"->"FD \nClone Build": stub sucessfully installed -"FD \nClone Build"->"Fraud\nDetection\nClone": build successful -"Loan\nIssuance"->"Loan\nIssuance": add a SCC\nStub Runner\ndependency\nand setup -"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nlocal storage -"LI\nSCC\nStub Runner"->"Local storage": find stubs of [FD] -"Local storage"->"LI\nSCC\nStub Runner": stubs of [FD] found -"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] -"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running -"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test -"Loan\nIssuance"->"Loan\nIssuance": run a test -"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub -"FD stub"->"Loan\nIssuance": stub responds successfuly -"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully -"Loan\nIssuance"->"Fraud\nDetection": send a pull request\nwith the\nsuggested contracts ----- - -*Start doing TDD by writing a test for your feature.* - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0] ----- - -Assume that you have written a test of your new feature. If a loan application for a big -amount is received, the system should reject that loan application with some description. - -*Write the missing implementation.* - -At some point in time, you need to send a request to the Fraud Detection service. Assume -that you need to send the request containing the ID of the client and the amount the -client wants to borrow. You want to send it to the `/fraudcheck` url via the `PUT` method. - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0] ----- - -For simplicity, the port of the Fraud Detection service is set to `8080`, and the -application runs on `8090`. - -If you start the test at this point, it breaks, because no service currently runs on port -`8080`. - -*Clone the Fraud Detection service repository locally.* - -You can start by playing around with the server side contract. To do so, you must first -clone it. - -[source,bash,indent=0] ----- -$ git clone https://your-git-server.com/server-side.git local-http-server-repo ----- - -*Define the contract locally in the repo of Fraud Detection service.* - -As a consumer, you need to define what exactly you want to achieve. You need to formulate -your expectations. To do so, write the following contract: - -IMPORTANT: Place the contract under `src/test/resources/contracts/fraud` folder. The `fraud` folder -is important because the producer's test base class name references that folder. - -==== -[source,groovy,indent=0,role="primary"] -.groovy ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[] ----- - -[source,yaml,indent=0,role="secondary"] -.yaml ----- -include::{introduction_url}/samples/standalone/yml/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.yml[] ----- -==== - -The YML contract is quite straight-forward. However when you take a look at the Contract -written using a statically typed Groovy DSL - you might wonder what the -`value(client(...), server(...))` parts are. By using this notation, Spring Cloud -Contract lets you define parts of a JSON block, a URL, etc., which are dynamic. In case -of an identifier or a timestamp, you need not hardcode a value. You want to allow some -different ranges of values. To enable ranges of values, you can set regular expressions -matching those values for the consumer side. You can provide the body by means of either -a map notation or String with interpolations. -//TODO: Ensure that the links are correct -Consult the <> section for more information. We highly recommend using the map notation! - -TIP: You must understand the map notation in order to set up contracts. Please read the -https://groovy-lang.org/json.html[Groovy docs regarding JSON]. - -The previously shown contract is an agreement between two sides that: - -- if an HTTP request is sent with all of -** a `PUT` method on the `/fraudcheck` endpoint, -** a JSON body with a `client.id` that matches the regular expression `[0-9]{10}` and -`loanAmount` equal to `99999`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`, -- then an HTTP response is sent to the consumer that -** has status `200`, -** contains a JSON body with the `fraudCheckStatus` field containing a value `FRAUD` and -the `rejectionReason` field having value `Amount too high`, -** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`. - -Once you are ready to check the API in practice in the integration tests, you need to -install the stubs locally. - -*Add the Spring Cloud Contract Verifier plugin.* - -We can add either a Maven or a Gradle plugin. In this example, you see how to add Maven. -First, add the `Spring Cloud Contract` BOM. - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_bom,indent=0] ----- - -Next, add the `Spring Cloud Contract Verifier` Maven plugin - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -Since the plugin was added, you get the `Spring Cloud Contract Verifier` features which, -from the provided contracts: - -- generate and run tests -- produce and install stubs - -You do not want to generate tests since you, as the consumer, want only to play with the -stubs. You need to skip the test generation and execution. When you execute: - -[source,bash,indent=0] ----- -$ cd local-http-server-repo -$ ./mvnw clean install -DskipTests ----- - -In the logs, you see something like this: - -[source,bash,indent=0] ----- -[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar -[INFO] -[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- -[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar -[INFO] -[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server --- -[INFO] -[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar -[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -The following line is extremely important: - -[source,bash,indent=0] ----- -[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar ----- - -It confirms that the stubs of the `http-server` have been installed in the local -repository. - -*Run the integration tests.* - -In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic -stub downloading, you must do the following in your consumer side project (`Loan -Application service`): - -Add the `Spring Cloud Contract` BOM: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=contract_bom,indent=0] ----- - -Add the dependency to `Spring Cloud Contract Stub Runner`: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0] ----- - -Annotate your test class with `@AutoConfigureStubRunner`. In the annotation, provide the -`group-id` and `artifact-id` for the Stub Runner to download the stubs of your -collaborators. (Optional step) Because you're playing with the collaborators offline, you -can also provide the offline work switch (`StubRunnerProperties.StubsMode.LOCAL`). - -[source,groovy,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0] ----- - -Now, when you run your tests, you see something like this: - -[source,bash,indent=0] ----- -2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version -2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT -2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] -2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar -2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] -2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] -2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}] ----- - -This output means that Stub Runner has found your stubs and started a server for your app -with group id `com.example`, artifact id `http-server` with version `0.0.1-SNAPSHOT` of -the stubs and with `stubs` classifier on port `8080`. - -*File a pull request.* - -What you have done until now is an iterative process. You can play around with the -contract, install it locally, and work on the consumer side until the contract works as -you wish. - -Once you are satisfied with the results and the test passes, publish a pull request to -the server side. Currently, the consumer side work is done. - -=== Producer side (Fraud Detection server) - -As a developer of the Fraud Detection server (a server to the Loan Issuance service) we would like to: - -- take over the pull request -- write the missing implementation -- deploy the application - -Let's look at the Fraud Detection flow: - -[plantuml, getting-started-cdc-server, png] ----- -"Fraud\nDetection"->"Fraud\nDetection": take over the\n pull request -"Fraud\nDetection"->"Fraud\nDetection": setup\nSpring Cloud\nContract plugin -"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build -"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) -"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated -"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests -"Fraud\nDetection\nBuild"->"Fraud\nDetection": generated tests failed! -"Fraud\nDetection"->"Fraud\nDetection": setup\nbase classes\nfor contract tests -"Fraud\nDetection"->"Fraud\nDetection\nBuild": run the build -"Fraud\nDetection\nBuild"->"SCC Plugin": generate tests\nstubs \nand stubs artifact \n(e.g. stubs-jar) -"SCC Plugin"->"Fraud\nDetection\nBuild": tests and stubs generated -"Fraud\nDetection\nBuild"->"Fraud\nDetection\nBuild": run tests -"Fraud\nDetection\nBuild"->"Fraud\nDetection": all the tests passed! -"Fraud\nDetection"->"Fraud\nDetection": commit and push changes -"Fraud\nDetection"->"CI": commit pushed!\nTriggers the build -"CI"->"Stub Storage": build successful,\nupload artifacts ----- - -*Take over the pull request.* - -As a reminder, you can see the initial implementation here: - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -[source,bash,indent=0] ----- -$ git checkout -b contract-change-pr master -$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr ----- - -You must add the dependencies needed by the autogenerated tests: - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0] ----- - -In the configuration of the Maven plugin, pass the `packageWithBaseClasses` property - -[source,xml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0] ----- - -IMPORTANT: This example uses "convention based" naming by setting the -`packageWithBaseClasses` property. Doing so means that the two last packages combine to -make the name of the base test class. In our case, the contracts were placed under -`src/test/resources/contracts/fraud`. Since you do not have two packages starting from -the `contracts` folder, pick only one, which should be `fraud`. Add the `Base` suffix and -capitalize `fraud`. That gives you the `FraudBase` test class name. - -All the generated tests extend that class. Over there, you can set up your Spring Context -or whatever is necessary. In this case, use https://github.com/rest-assured/rest-assured[Rest Assured MVC] to -start the server side `FraudDetectionController`. - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/test/java/com/example/fraud/FraudBase.java[] ----- - -Now, if you run the `./mvnw clean install`, you get something like this: - -[source,bash,indent=0] ----- -Results : - -Tests in error: - ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed... ----- - -This error occurs because you have a new contract from which a test was generated and it -failed since you have not implemented the feature. The auto-generated test would look -like this: - -[source,java,indent=0] ----- -@Test -public void validate_shouldMarkClientAsFraud() throws Exception { - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/vnd.fraud.v1+json") - .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); - - // when: - ResponseOptions response = given().spec(request) - .put("/fraudcheck"); - - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); - assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); -} ----- - -If you used the Groovy DSL, you can see, all the `producer()` parts of the Contract that were present in the -`value(consumer(...), producer(...))` blocks got injected into the test. -In case of using YAML, the same applied for the `matchers` sections of the `response`. - -Note that, on the producer side, you are also doing TDD. The expectations are expressed -in the form of a test. This test sends a request to our own application with the URL, -headers, and body defined in the contract. It also is expecting precisely defined values -in the response. In other words, you have the `red` part of `red`, `green`, and -`refactor`. It is time to convert the `red` into the `green`. - -*Write the missing implementation.* - -Because you know the expected input and expected output, you can write the missing -implementation: - -[source,java,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0] -include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0] -} ----- - -When you execute `./mvnw clean install` again, the tests pass. Since the `Spring Cloud -Contract Verifier` plugin adds the tests to the `generated-test-sources`, you can -actually run those tests from your IDE. - -*Deploy your app.* - -Once you finish your work, you can deploy your change. First, merge the branch: - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr -$ git push origin master ----- - -Your CI might run something like `./mvnw clean deploy`, which would publish both the -application and the stub artifacts. - -=== Consumer Side (Loan Issuance) Final Step - -As a developer of the Loan Issuance service (a consumer of the Fraud Detection server) we will want to: - -- merge our feature branch to `master` -- switch to online mode of working - - -[plantuml, getting-started-cdc-client, png] ----- -"Loan\nIssuance"->"Loan\nIssuance": merge the\nfeature branch\nto master branch -"Loan\nIssuance"->"Loan\nIssuance": setup SCC Stub Runner\nto fetch stubs\nfrom Stub Storage -"Loan\nIssuance"->"LI\nSCC\nStub Runner": start stubs\nof FD from\nStub Storage -"LI\nSCC\nStub Runner"->"Stub Storage": find stubs of [FD] -"Stub Storage"->"LI\nSCC\nStub Runner": stubs of [FD] found -"LI\nSCC\nStub Runner"->"FD stub": run stubs of [FD] -"FD stub"->"LI\nSCC\nStub Runner": [FD] stub is running -"LI\nSCC\nStub Runner"->"Loan\nIssuance": stubs running and ready for the test -"Loan\nIssuance"->"Loan\nIssuance": run a test -"Loan\nIssuance"->"FD stub": the test\nsends a request\nto the running stub -"FD stub"->"Loan\nIssuance": stub responds successfuly -"Loan\nIssuance"->"Loan\nIssuance": the test passes successfully ----- - -*Merge branch to master.* - -[source,bash,indent=0] ----- -$ git checkout master -$ git merge --no-ff contract-change-pr ----- - -*Work online.* - -Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate -where the repository with your stubs is located. At this moment the stubs of the server -side are automatically downloaded from Nexus/Artifactory. You can set the value of -`stubsMode` to `REMOTE`. The following code shows an example of -achieving the same thing by changing the properties. - -[source,yaml,indent=0] ----- -include::{introduction_url}/samples/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[] ----- - -That's it! - -[[getting-started-whats-next]] -== What to Read Next -Hopefully, this section provided some of the {project-full-name} basics and got you on your way -to writing your own applications. If you are a task-oriented type of developer, you might -want to jump over to https://spring.io and check out some of the -https://spring.io/guides/[getting started] guides that solve specific "`How do I do that -with Spring?`" problems. We also have {project-full-name}-specific -"`<>`" reference documentation. - -Otherwise, the next logical step is to read _<>_. If -you are really impatient, you could also jump ahead and read about -_<>_. - -// TODO: Check if we can link courses -In addition to that you can check out the following videos: - -- "Consumer Driven Contracts and Your Microservice Architecture" by Olga Maciaszek-Sharma and Marcin Grzejszczak - -video::pDkC_00hhvA[youtube,width=640,height=480] - -- "Contract Tests in the Enterprise" by Marcin Grzejszczak - -video::ZyHG-VOzPZg[youtube,width=640,height=480] - -- "Why Contract Tests Matter?" by Marcin Grzejszczak - -video::TvpkZu1e2Dc[youtube,start=6262,width=640,height=480] - -You can find the default project samples at -https://github.com/spring-cloud-samples/spring-cloud-contract-samples[samples]. - -You can find the Spring Cloud Contract workshops https://cloud-samples.spring.io/spring-cloud-contract-samples/[over here]. \ No newline at end of file +include::_getting-started.adoc[] \ No newline at end of file diff --git a/docs/src/main/asciidoc/howto.adoc b/docs/src/main/asciidoc/howto.adoc index bea57644cd..e3e0f1db3c 100644 --- a/docs/src/main/asciidoc/howto.adoc +++ b/docs/src/main/asciidoc/howto.adoc @@ -714,25 +714,247 @@ to store and share Pact definitions. Starting from Spring Cloud Contract 2.0.0 one can fetch Pact files from the Pact Broker to generate tests and stubs. -As a prerequisite the Pact Converter and Pact Stub Downloader -are required. You have to add them via the `spring-cloud-contract-pact` dependency. -You can read more about it in the <> section. - IMPORTANT: Pact follows the Consumer Contract convention. That means that the Consumer creates the Pact definitions first, then shares the files with the Producer. Those expectations are generated from the Consumer's code and can break the Producer if the expectations are not met. +[[how-to-use-pact-broker-pact]] +=== How to work with Pact + +Spring Cloud Contract includes support for https://docs.pact.io/[Pact] representation of +contracts up until v4. Instead of using the DSL, you can use Pact files. In this section, we +present how to add Pact support for your project. Note however that not all functionality is supported. +Starting with v3 you can combine multiple matcher for the same element; +you can use matchers for the body, headers, request and path; and you can use value generators. +Spring Cloud Contract currently only supports multiple matchers that are combined using the AND rule logic. +Next to that the request and path matchers are skipped during the conversion. +When using a date, time or datetime value generator with a given format, +the given format will be skipped and the ISO format will be used. + +[[how-to-use-pact-broker-pact-converter]] +=== Pact Converter + +In order to properly support the Spring Cloud Contract way of doing messaging +with Pact you'll have to provide some additional meta data entries. Below you can find a list of such entries: + +* to define the destination to which a message gets sent, you have to +set +** `metaData` entry in the Pact file +*** with key `sentTo` equal to the destination to which a message is to be sent. +*** E.g. `"metaData": { "sentTo": "activemq:output" }` + +[[how-to-use-pact-broker-pact-contract]] +=== Pact Contract + +Spring Cloud Contract can read the Pact JSON definition out of the box. It's enough to place the file under `src/test/resources/contracts` folder. An example of such a Pact contract can look like this: + +[source,javascript,indent=0] +---- +include::{standalone_pact_path}/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json[indent=0] +---- + +[[how-to-use-pact-broker-pact-for-producers]] +=== Pact for Producers + +On the producer side, you must add two additional dependencies to your plugin +configuration. One is the Spring Cloud Contract Pact support, and the other represents +the current Pact version that you use. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{standalone_pact_path}/pact-http-server/pom.xml[tags=pact_dependency,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{standalone_pact_path}/pact-http-server/build.gradle[tags=pact_dependency,indent=0] +---- +==== + +When you execute the build of your application, a test and stub will be generated. + +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.test +---- +@Test + public void validate_shouldMarkClientAsFraud() throws Exception { + // given: + MockMvcRequestSpecification request = given() + .header("Content-Type", "application/vnd.fraud.v1+json") + .body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}"); + + // when: + ResponseOptions response = given().spec(request) + .put("/fraudcheck"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/vnd\\.fraud\\.v1\\+json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("['rejectionReason']").isEqualTo("Amount too high"); + // and: + assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD"); + } +---- + +[source,json,indent=0,subs="verbatim,attributes",role="secondary"] +.stub +---- +{ + "id" : "996ae5ae-6834-4db6-8fac-358ca187ab62", + "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62", + "request" : { + "url" : "/fraudcheck", + "method" : "PUT", + "headers" : { + "Content-Type" : { + "matches" : "application/vnd\\.fraud\\.v1\\+json.*" + } + }, + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.['loanAmount'] = 99999)]" + }, { + "matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]" + } ] + }, + "response" : { + "status" : 200, + "body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}", + "headers" : { + "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8" + }, + "transformers" : [ "response-template" ] + }, +} +---- +==== + +[[how-to-use-pact-broker-pact-consumers]] +=== Pact for Consumers + +On the producer side, you must add two additional dependencies to your project +dependencies. One is the Spring Cloud Contract Pact support, and the other represents the +current Pact version that you use. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven +---- +include::{standalone_pact_path}/pact-http-client/pom.xml[tags=pact_dependency,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +include::{standalone_pact_path}/pact-http-client/build.gradle[tags=pact_dependency,indent=0] +---- +==== + +[[pact-stub-downloader]] +=== Communicating with the Pact Broker + +Whenever the `repositoryRoot` starts with a Pact protocol +(starts with `pact://`), the stub downloader will try +to fetch the Pact contract definitions from the Pact Broker. +Whatever is set after `pact://` will be parsed as the Pact Broker URL. + +Either via environment variables, system properties, properties set +inside the plugin or contracts repository configuration you can +tweak the downloader's behaviour. Below you can find the list of +properties + +.Pact Stub Downloader properties +|==== +|Name of a property |Default | Description +| +* `pactbroker.host` (plugin prop) + +* `stubrunner.properties.pactbroker.host` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_HOST` (env prop) +|Host from URL passed to `repositoryRoot` +|What is the URL of Pact Broker + +| +* `pactbroker.port` (plugin prop) + +* `stubrunner.properties.pactbroker.port` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_PORT` (env prop) +|Port from URL passed to `repositoryRoot` +|What is the port of Pact Broker + +| +* `pactbroker.protocol` (plugin prop) + +* `stubrunner.properties.pactbroker.protocol` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL` (env prop) +|Protocol from URL passed to `repositoryRoot` +|What is the protocol of Pact Broker + +| +* `pactbroker.tags` (plugin prop) + +* `stubrunner.properties.pactbroker.tags` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_TAGS` (env prop) +|Version of the stub, or `latest` if version is `+` +|What tags should be used to fetch the stub + +| +* `pactbroker.auth.scheme` (plugin prop) + +* `stubrunner.properties.pactbroker.auth.scheme` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME` (env prop) +|`Basic` +|What kind of authentication should be used to connect to the Pact Broker + +| +* `pactbroker.auth.username` (plugin prop) + +* `stubrunner.properties.pactbroker.auth.username` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME` (env prop) +|The username passed to `contractsRepositoryUsername` (maven) or `contractRepository.username` (gradle) +|Username used to connect to the Pact Broker + +| +* `pactbroker.auth.password` (plugin prop) + +* `stubrunner.properties.pactbroker.auth.password` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD` (env prop) +|The password passed to `contractsRepositoryPassword` (maven) or `contractRepository.password` (gradle) +|Password used to connect to the Pact Broker + +| +* `pactbroker.provider-name-with-group-id` (plugin prop) + +* `stubrunner.properties.pactbroker.provider-name-with-group-id` (system prop) + +* `STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID` (env prop) +|false +|When `true`, the provider name will be a combination of `groupId:artifactId`. If `false`, just `artifactId` is used +|==== + [[how-to-pact-consumer]] -=== Pact Consumer +=== Flow: Consumer Contract approach with Pact Broker | Consumer Side The consumer uses Pact framework to generate Pact files. The Pact files are sent to the Pact Broker. An example of such setup can be found https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/consumer_pact[here]. [[how-to-pact-producer]] -=== Producer +=== Flow: Consumer Contract approach with Pact Broker | Producer Side For the producer, to use the Pact files from the Pact Broker, we can reuse the same mechanism we use for external contracts. We route Spring Cloud Contract @@ -816,7 +1038,7 @@ With such a setup: * The JAR with the stubs gets automatically created as usual [[how-to-pact-consumer-producer-contract]] -=== Pact Consumer (Producer Contract approach) +=== Flow: Producer Contract approach with Pact | Consumer Side In the scenario where you don't want to do Consumer Contract approach (for every single consumer define the expectations) but you'd prefer @@ -900,9 +1122,6 @@ With such a setup: * Spring Cloud Contract will convert the Pact files into stub definitions * The stub servers will be started and fed with stubs -For more information about Pact support you can go to -the <> section. - [[how-to-debug]] == How can I debug the request/response being sent by the generated tests client? @@ -1048,4 +1267,43 @@ For example, you might decide to have no dependencies at all. [[how-to-work-with-transitivie-exclude]] === Exclude dependencies on the consumer side -As a consumer, if you add the stub dependency to your classpath, you can explicitly exclude the unwanted dependencies. \ No newline at end of file +As a consumer, if you add the stub dependency to your classpath, you can explicitly exclude the unwanted dependencies. + +[[contract-dsl-rest-docs]] +== Generating Spring REST Docs snippets from the contracts + +When you want to include the requests and responses of your API using Spring REST Docs, +you only need to make some minor changes to your setup if you are using MockMvc and RestAssuredMockMvc. +Simply include the following dependencies if you haven't already. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.maven +---- +include::{standalone_restdocs_path}/http-server/pom.xml[tags=dependencies,indent=0] +---- + +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.gradle +---- +include::{standalone_restdocs_path}/http-server/build.gradle[tags=dependencies,indent=0] +---- +==== + +Next you need to make some changes to your base class. Below you can find examples for using `WebAppContext` and the standalone option when using RestAssured. + +==== +[source,java,indent=0,subs="verbatim,attributes",role="primary"] +.WebAppContext +---- +include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithWebAppSetup.java[tags=base_class,indent=0] +---- + +[source,java,indent=0,subs="verbatim,attributes",role="secondary"] +.Standalone +---- +include::{standalone_restdocs_path}/http-server/src/test/java/com/example/fraud/FraudBaseWithStandaloneSetup.java[tags=base_class,indent=0] +---- +==== + +TIP: You don't need to specify the output directory for the generated snippets since version 1.2.0.RELEASE of Spring REST Docs. \ No newline at end of file diff --git a/docs/src/main/asciidoc/index.htmladoc b/docs/src/main/asciidoc/index.htmladoc index cc009df9f7..eb633d129e 100644 --- a/docs/src/main/asciidoc/index.htmladoc +++ b/docs/src/main/asciidoc/index.htmladoc @@ -11,8 +11,6 @@ The reference documentation consists of the following sections: <> :: About the Documentation, Getting Help, First Steps, and more. <> :: Introducing {project-full-name}, System Requirements, Developing Your First {project-full-name} based Application <> :: {project-full-name} usage examples and workflows -<> :: Profiles, Logging, Security, Caching, Spring Integration, Testing, and more. -<> :: Monitoring, Metrics, Auditing, and more. -<> :: Maven Plugin, Gradle Plugin. -<> :: Application Development, Configuration, Embedded Servers, Data Access, and many more. -<> :: Properties, Metadata, Configuration, Dependencies, and more. +<> :: Profiles, Logging, Security, Caching, Spring Integration, Testing, and more.W +<> :: Maven Plugin, Gradle Plugin, Docker +<> :: Stubs versioning, Pact integration, Debugging, and more. diff --git a/docs/src/main/asciidoc/index.htmlpdfadoc b/docs/src/main/asciidoc/index.htmlpdfadoc index 5e8932ca92..46603b6857 100644 --- a/docs/src/main/asciidoc/index.htmlpdfadoc +++ b/docs/src/main/asciidoc/index.htmlpdfadoc @@ -9,6 +9,4 @@ include::documentation-overview.adoc[leveloffset=+1] include::getting-started.adoc[leveloffset=+1] include::using.adoc[leveloffset=+1] include::project-features.adoc[leveloffset=+1] -include::build-tool-plugins.adoc[leveloffset=+1] -include::howto.adoc[leveloffset=+1] -include::appendix.adoc[leveloffset=+1] \ No newline at end of file +include::howto.adoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/src/main/asciidoc/index.htmlsingleadoc b/docs/src/main/asciidoc/index.htmlsingleadoc index 5e8932ca92..46603b6857 100644 --- a/docs/src/main/asciidoc/index.htmlsingleadoc +++ b/docs/src/main/asciidoc/index.htmlsingleadoc @@ -9,6 +9,4 @@ include::documentation-overview.adoc[leveloffset=+1] include::getting-started.adoc[leveloffset=+1] include::using.adoc[leveloffset=+1] include::project-features.adoc[leveloffset=+1] -include::build-tool-plugins.adoc[leveloffset=+1] -include::howto.adoc[leveloffset=+1] -include::appendix.adoc[leveloffset=+1] \ No newline at end of file +include::howto.adoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/src/main/asciidoc/project-features.adoc b/docs/src/main/asciidoc/project-features.adoc index f5614b1b18..8666a0fff6 100644 --- a/docs/src/main/asciidoc/project-features.adoc +++ b/docs/src/main/asciidoc/project-features.adoc @@ -8,6 +8,24 @@ might want to read the "<>" and "<>" sections, so that you have a good grounding of the basics. +include::_project-features-contract.adoc[] + +include::_project-features-flows.adoc[] + +include::_project-features-messaging.adoc[] + +include::_project-features-stubrunner.adoc[] + +include::_project-features-wiremock.adoc[] + +[[features-build-tools]] +== Build Tools integration + +You can run test generation / stub execution in various ways. The most common ones are + +* link:maven-project.html[Maven] +* link:gradle-project.html[Gradle] +* link:docker-project.html[Docker] [[features-whats-next]] == What to Read Next @@ -16,4 +34,5 @@ If you want to learn more about any of the classes discussed in this section, yo <> section. If you are comfortable with {project-full-name}'s core features, you can continue on and read -about ... +about +_<>_. diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java index ad098c697d..4bf7e7e900 100644 --- a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java @@ -21,6 +21,7 @@ package org.springframework.cloud.contract.spec.internal; */ interface RegexCreatingProperty { + // tag::regex_creating_props[] T anyAlphaUnicode(); T anyAlphaNumeric(); @@ -62,5 +63,6 @@ interface RegexCreatingProperty { T anyNonEmptyString(); T anyOf(String... values); + // end::regex_creating_props[] } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java index 44fec08967..4c44099a97 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/junit/StubRunnerRuleJUnitTest.java @@ -62,7 +62,6 @@ public class StubRunnerRuleJUnitTest { return ""; } } - // end::test[] // tag::test[] @Test @@ -90,6 +89,7 @@ public class StubRunnerRuleJUnitTest { then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")) .isEqualTo("fraudDetectionServer"); } + // end::test[] private String httpGet(String url) throws Exception { try (InputStream stream = URI.create(url).toURL().openStream()) { diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy index dc09c61c8f..01a5d961bd 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy @@ -101,6 +101,22 @@ class ContractHttpDocsSpec extends Specification { } // end::http_dsl[] + org.springframework.cloud.contract.spec.Contract methodDsl = + org.springframework.cloud.contract.spec.Contract.make { + request { + // tag::method[] + method GET() + // end::method[] + url "/foo" + } + + response { + status 200 + } + + priority 1 + } + org.springframework.cloud.contract.spec.Contract request = // tag::request[] org.springframework.cloud.contract.spec.Contract.make { diff --git a/spring-cloud-contract-verifier/src/test/resources/yml/contract.yml b/spring-cloud-contract-verifier/src/test/resources/yml/contract.yml index 1cf282d093..6f1a02c319 100644 --- a/spring-cloud-contract-verifier/src/test/resources/yml/contract.yml +++ b/spring-cloud-contract-verifier/src/test/resources/yml/contract.yml @@ -21,7 +21,7 @@ request: queryParameters: a: b b: c - #tag::query_params[] + #end::query_params[] #tag::headers[] headers: foo: bar diff --git a/tests/spring-cloud-contract-stub-runner-amqp/README.adoc b/tests/spring-cloud-contract-stub-runner-amqp/README.adoc deleted file mode 100644 index 033e4fa094..0000000000 --- a/tests/spring-cloud-contract-stub-runner-amqp/README.adoc +++ /dev/null @@ -1,125 +0,0 @@ -=== Stub Runner Spring AMQP - -Spring Cloud Contract Verifier Stub Runner's messaging module provides an easy way to -integrate with Spring AMQP's Rabbit Template. For the provided artifacts, it -automatically downloads the stubs and registers the required routes. - -The integration tries to work standalone (that is, without interaction with a running -RabbitMQ message broker). It expects a `RabbitTemplate` on the application context and -uses it as a spring boot test named `@SpyBean`. As a result, it can use the mockito spy -functionality to verify and inspect messages sent by the application. - -On the message consumer side, the stub runner considers all `@RabbitListener` annotated -endpoints and all `SimpleMessageListenerContainer` objects on the application context. - -As messages are usually sent to exchanges in AMQP, the message contract contains the -exchange name as the destination. Message listeners on the other side are bound to -queues. Bindings connect an exchange to a queue. If message contracts are triggered, the -Spring AMQP stub runner integration looks for bindings on the application context that -match this exchange. Then it collects the queues from the Spring exchanges and tries to -find message listeners bound to these queues. The message is triggered for all matching -message listeners. - -If you need to work with routing keys, it's enough to pass them via the `amqp_receivedRoutingKey` -messaging header. - -==== Adding the Runner to the Project - -You can have both Spring AMQP and Spring Cloud Contract Stub Runner on the classpath and -set the property `stubrunner.amqp.enabled=true`. Remember to annotate your test class -with `@AutoConfigureStubRunner`. - -IMPORTANT: If you already have Stream and Integration on the classpath, you need -to disable them explicitly by setting the `stubrunner.stream.enabled=false` and -`stubrunner.integration.enabled=false` properties. - -Assume that you have the following Maven repository with a deployed stubs for the -`spring-cloud-contract-amqp-test` application. - -[source,bash,indent=0] ----- -└── .m2 - └── repository - └── com - └── example - └── spring-cloud-contract-amqp-test - ├── 0.4.0-SNAPSHOT - │   ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom - │   ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar - │   └── maven-metadata-local.xml - └── maven-metadata-local.xml ----- - -Further assume that the stubs contain the following structure: - -[source,bash,indent=0] ----- -├── META-INF -│   └── MANIFEST.MF -└── contracts - └── shouldProduceValidPersonData.groovy ----- - -Consider the following contract: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy[tags=amqp_contract,indent=0] ----- - -Now consider the following Spring configuration: - -[source,yaml] ----- -include::src/test/resources/application.yml[] ----- - -===== Triggering the message - -To trigger a message using the contract above, use the `StubTrigger` interface as -follows: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy[tags=client_trigger,indent=0] ----- - -The message has a destination of `contract-test.exchange`, so the Spring AMQP stub runner -integration looks for bindings related to this exchange. - -[source,java] ----- -include::src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpMessagingApplication.java[tags=amqp_binding,indent=0] ----- - -The binding definition binds the queue `test.queue`. As a result, the following listener -definition is matched and invoked with the contract message. - -[source,java] ----- -include::src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpMessagingApplication.java[tags=amqp_listener,indent=0] ----- - -Also, the following annotated listener matches and is invoked: -[source,java] ----- -include::src/main/java/org/springframework/cloud/contract/stubrunner/messaging/amqp/MessageSubscriberRabbitListener.java[tags=amqp_annotated_listener,indent=0] ----- - -NOTE: The message is directly handed over to the `onMessage` method of the -`MessageListener` associated with the matching `SimpleMessageListenerContainer`. - -===== Spring AMQP Test Configuration - -In order to avoid Spring AMQP trying to connect to a running broker during our tests -configure a mock `ConnectionFactory`. - -To disable the mocked ConnectionFactory, set the following property: -`stubrunner.amqp.mockConnection=false` - -[source,yaml] ----- -stubrunner: - amqp: - mockConnection: false ----- diff --git a/tests/spring-cloud-contract-stub-runner-camel/README.adoc b/tests/spring-cloud-contract-stub-runner-camel/README.adoc deleted file mode 100644 index 30c50c8e55..0000000000 --- a/tests/spring-cloud-contract-stub-runner-camel/README.adoc +++ /dev/null @@ -1,123 +0,0 @@ -:input_name: jms:input -:output_name: jms:output - -=== Stub Runner Camel - -Spring Cloud Contract Verifier 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 - -It's enough to have both Apache Camel and Spring Cloud Contract Stub Runner on classpath. -Remember to annotate your test class with `@AutoConfigureStubRunner`. - -==== Disabling the functionality - -If you need to disable this functionality just pass `stubrunner.camel.enabled=false` property. - -==== Examples - -===== Stubs structure - -Let us assume that we have the following Maven repository with a deployed stubs for the -`camelService` application. - -[source,bash,indent=0] ----- -└── .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: - -[source,bash,indent=0] ----- -├── META-INF -│   └── MANIFEST.MF -└── repository - ├── accurest - │   ├── bookDeleted.groovy - │   ├── bookReturned1.groovy - │   └── bookReturned2.groovy - └── mappings ----- - -Let's consider the following contracts (let' number it with *1*): - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl,indent=0] ----- - -and number *2* - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] ----- - -===== 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 - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger,indent=0] ----- - -Next we'll want to listen to the output of the message sent to `{output_name}` - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] ----- - -And the received message would pass the following assertions - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] ----- - -===== Scenario 2 (output triggered by input) - -Since the route is set for you it's enough to just send a message to the `{output_name}` destination. - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_send,indent=0] ----- - -Next we'll want to listen to the output of the message sent to `{output_name}` - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive,indent=0] ----- - -And the received message would pass the following assertions - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive_message,indent=0] ----- - -===== Scenario 3 (input with no output) - -Since the route is set for you it's enough to just send a message to the `{output_name}` destination. - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] ----- diff --git a/tests/spring-cloud-contract-stub-runner-integration/README.adoc b/tests/spring-cloud-contract-stub-runner-integration/README.adoc deleted file mode 100644 index ccae044b9a..0000000000 --- a/tests/spring-cloud-contract-stub-runner-integration/README.adoc +++ /dev/null @@ -1,138 +0,0 @@ -:input_name: input -:output_name: output - -=== Stub Runner Integration - -Spring Cloud Contract Verifier Stub Runner's messaging module gives you an easy way to -integrate with Spring Integration. For the provided artifacts, it automatically downloads -the stubs and registers the required routes. - -==== Adding the Runner to the Project - -You can have both Spring Integration and Spring Cloud Contract Stub Runner on the -classpath. Remember to annotate your test class with `@AutoConfigureStubRunner`. - -==== Disabling the functionality - -If you need to disable this functionality, set the -`stubrunner.integration.enabled=false` property. - -Assume that you have the following Maven repository with deployed stubs for the -`integrationService` application: - -[source,bash,indent=0] ----- -└── .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 ----- - -Further assume the stubs contain the following structure: - -[source,bash,indent=0] ----- -├── META-INF -│   └── MANIFEST.MF -└── repository - ├── accurest - │   ├── bookDeleted.groovy - │   ├── bookReturned1.groovy - │   └── bookReturned2.groovy - └── mappings ----- - -Consider the following contracts (numbered *1*): - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=sample_dsl,indent=0] ----- - -Now consider *2*: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] ----- - -and the following Spring Integration Route: - -[source,xml] ----- -include::src/test/resources/integration-context.xml[lines=1;18..-1] ----- - -These examples lend themselves to three scenarios: - -* <> -* <> -* <> - -[[integration-scenario-1]] -===== Scenario 1 (no input message) - -To trigger a message via the `return_book_1` label, use the `StubTigger` interface, as -follows: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger,indent=0] ----- - -To listen to the output of the message sent to `{output_name}`: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] ----- - -The received message would pass the following assertions: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] ----- - -[[integration-scenario-2]] -===== Scenario 2 (output triggered by input) - -Since the route is set for you, you can send a message to the `{output_name}` -destination: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_send,indent=0] ----- - -To listen to the output of the message sent to `{output_name}`: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_receive,indent=0] ----- - -The received message passes the following assertions: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=client_receive_message,indent=0] ----- - -[[integration-scenario-3]] -===== Scenario 3 (input with no output) - -Since the route is set for you, you can send a message to the `{input_name}` destination: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/integration/IntegrationStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] ----- diff --git a/tests/spring-cloud-contract-stub-runner-stream/README.adoc b/tests/spring-cloud-contract-stub-runner-stream/README.adoc deleted file mode 100644 index fad7b0ad93..0000000000 --- a/tests/spring-cloud-contract-stub-runner-stream/README.adoc +++ /dev/null @@ -1,160 +0,0 @@ -=== Stub Runner Stream - -Spring Cloud Contract Verifier Stub Runner's messaging module gives you an easy way to -integrate with Spring Stream. For the provided artifacts, it automatically downloads the -stubs and registers the required routes. - -WARNING: If Stub Runner's integration with Stream the `messageFrom` or `sentTo` Strings -are resolved first as a `destination` of a channel and no such `destination` exists, the -destination is resolved as a channel name. - -IMPORTANT: If you want to use Spring Cloud Stream remember, to add a dependency on -`org.springframework.cloud:spring-cloud-stream-test-support`. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven ----- - - org.springframework.cloud - spring-cloud-stream-test-support - test - ----- - -[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] -.Gradle ----- -testCompile "org.springframework.cloud:spring-cloud-stream-test-support" ----- - -==== Adding the Runner to the Project - -You can have both Spring Cloud Stream and Spring Cloud Contract Stub Runner on the -classpath. Remember to annotate your test class with `@AutoConfigureStubRunner`. - -==== Disabling the functionality - -If you need to disable this functionality, set the `stubrunner.stream.enabled=false` -property. - -Assume that you have the following Maven repository with a deployed stubs for the -`streamService` application: - -[source,bash,indent=0] ----- -└── .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 ----- - -Further assume the stubs contain the following structure: - -[source,bash,indent=0] ----- -├── META-INF -│   └── MANIFEST.MF -└── repository - ├── accurest - │   ├── bookDeleted.groovy - │   ├── bookReturned1.groovy - │   └── bookReturned2.groovy - └── mappings ----- - -Consider the following contracts (numbered *1*): - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=sample_dsl,indent=0] ----- - -Now consider *2*: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0] ----- - -Now consider the following Spring configuration: - -[source,yaml] ----- -include::src/test/resources/application.yml[] ----- - -These examples lend themselves to three scenarios: - -* <> -* <> -* <> - -[[stream-scenario-1]] -===== Scenario 1 (no input message) - -To trigger a message via the `return_book_1` label, use the `StubTrigger` interface as -follows: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger,indent=0] ----- - -To listen to the output of the message sent to a channel whose `destination` is -`returnBook`: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0] ----- - -The received message passes the following assertions: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_trigger_message,indent=0] ----- - -[[stream-scenario-2]] -===== Scenario 2 (output triggered by input) - -Since the route is set for you, you can send a message to the `bookStorage` -`destination`: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_send,indent=0] ----- - -To listen to the output of the message sent to `returnBook`: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_receive,indent=0] ----- - -The received message passes the following assertions: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=client_receive_message,indent=0] ----- - -[[stream-scenario-3]] -===== Scenario 3 (input with no output) - -Since the route is set for you, you can send a message to the `{output_name}` -destination: - -[source,groovy] ----- -include::src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy[tags=trigger_no_output,indent=0] -----