diff --git a/spring-cloud-contract-maven-plugin/plugins.html b/spring-cloud-contract-maven-plugin/plugins.html index 576e56451d..2a6e2c5dac 100644 --- a/spring-cloud-contract-maven-plugin/plugins.html +++ b/spring-cloud-contract-maven-plugin/plugins.html @@ -405,7 +405,7 @@ org.jacoco http://jacoco-maven-plugin -0.7.8 +0.7.7.201606060606

Project Report Plugins

diff --git a/spring-cloud-contract.html b/spring-cloud-contract.html index 477c7b8d83..5513114e44 100644 --- a/spring-cloud-contract.html +++ b/spring-cloud-contract.html @@ -785,16 +785,39 @@ $(addBlockSwitches);
  • Extending the DSL
  • -
  • Converters
  • -
  • Stub converters
  • -
  • Example of a custom HTTP Server Stub
  • -
  • Example of a custom Stub Downloader Provider
  • +
  • Pluggable architecture + +
  • +
  • Links
  • + + @@ -7610,52 +7633,80 @@ public class ConsumerUtils {
    -
    Unresolved directive in verifier/contract.adoc - include::https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/master/common/src/main/java/com/example/ProducerUtils.java[]
    +
    package com.example;
     
    -===== Adding the dependency to project
    +import org.springframework.cloud.contract.spec.internal.ServerDslProperty;
     
    -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.
    +/**
    + * DSL Properties passed to the DSL from the producer's perspective.
    + * That means that on the input side {@code Request} for HTTP
    + * or {@code Input} for messaging you have to have a concrete value.
    + * On the {@code Response} for HTTP or {@code Output} for messaging
    + * you can have a regular expression.
    + *
    + * @author Marcin Grzejszczak
    + */
    +public class ProducerUtils {
     
    -====== Test dependency in project's dependencies
    -
    -First add the common jar dependency as a test dependency. That way since your
    +	/**
    +	 * Producer side property. By using the {@link ProducerUtils}
    +	 * you can omit most of boilerplate code from the perspective
    +	 * of dynamic values. Example
    +	 *
    +	 * <pre>
    +	 * {@code
    +	 * response {
    +	 *     body(
    +	 *         [ status: $(ProducerUtils.ok())]
    +	 *     )
    +	 * }
    +	 * </pre>
    +	 *
    +	 * That way the producer side value of age field will be
    +	 * a regular expression and the consumer side will be generated.
    +	 */
    +	public static ServerDslProperty ok() {
    +		return new ServerDslProperty(PatternUtils.ok());
    +	}
    +}
    +
    +
    + +
    +
    Adding the dependency to 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.

    +
    +
    +
    Test dependency in project’s dependencies
    +
    +

    First add the common jar dependency as a test dependency. That way since your contracts files are available at test resources path, automatically the -common jar classes will be visible in your Groovy files. - -[source,xml,indent=0,subs="verbatim,attributes",role="primary"] -.Maven +common jar classes will be visible in your Groovy files.

    -
    -
    -

    <dependency> - <groupId>com.example</groupId> - <artifactId>beer-common</artifactId> - <version>${project.version}</version> - <scope>test</scope> -</dependency>

    -
    -
    +
    +
    Maven
    -
    [source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
    -.Gradle
    +
    Unresolved directive in verifier/contract.adoc - include::https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/master/producer/pom.xml[tags=test_dep,indent=0]
    -
    -

    testCompile("com.example:beer-common:0.0.1-SNAPSHOT")

    -
    -
    +
    +
    Gradle
    -
    ====== Test dependency in plugin's dependencies
    -
    -Now you have to add the dependency for the plugin to reuse at runtime.
    -
    -[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
    -.Maven
    +
    testCompile("com.example:beer-common:0.0.1-SNAPSHOT")
    +
    +
    +
    Test dependency in plugin’s dependencies
    -

    <plugin> +

    Now you have to add the dependency for the plugin to reuse at runtime.

    +
    +
    +
    Maven
    +
    +
    <plugin>
     	<groupId>org.springframework.cloud</groupId>
     	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
     	<version>${spring-cloud-contract.version}</version>
    @@ -7676,49 +7727,41 @@ Now you have to add the dependency for the plugin to reuse at runtime.
     			<scope>compile</scope>
     		</dependency>
     	</dependencies>
    -</plugin>

    +</plugin>
    +
    +
    +
    +
    Gradle
    +
    +
    classpath "com.example:beer-common:0.0.1-SNAPSHOT"
    +
    +
    +
    +
    +
    Referencing classes in DSLs
    +
    +

    Now you can reference your classes in your DSL. Example:

    -
    [source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
    -.Gradle
    -
    -
    -
    -

    classpath "com.example:beer-common:0.0.1-SNAPSHOT"

    -
    -
    -
    -
    ====== Referencing classes in DSLs
    +
    package contracts.beer.rest
     
    -Now you can reference your classes in your DSL. Example:
    +import org.springframework.cloud.contract.spec.Contract
     
    -[source,groovy]
    -
    -
    -
    -

    package contracts.beer.rest

    -
    -
    -

    import org.springframework.cloud.contract.spec.Contract

    -
    -
    -

    import static com.example.ConsumerUtils.oldEnough -import static com.example.ProducerUtils.ok

    -
    -
    -

    Contract.make { +import static com.example.ConsumerUtils.oldEnough +import static com.example.ProducerUtils.ok + +Contract.make { request { description(""" -Represents a successful scenario of getting a beer

    -
    -
    -

    given: +Represents a successful scenario of getting a beer + +given: client is old enough when: he applies for a beer then: - we’ll grant him the beer + we'll grant him the beer """) method 'POST' url '/check' @@ -7740,28 +7783,31 @@ then: contentType(applicationJson()) } } -}

    +}
    +
    +
    +
    +
    + + +
    +

    Pluggable architecture

    +
    +

    There are cases where you have your contracts defined in other formats +like YAML, RAML or PACT. On the other hand you’d like to profit from +the test and stubs generation. It’s really easy to add your own implementation +of either of those. Also you can customize the way tests are generated (for example you can generate +tests for other languages) and you can do the same for stubs generation (you can generate +stubs for other stub http server implementations).

    +
    +
    +

    Custom contract converter

    +
    +

    Let’s assume that your contract is written in a YAML file like this:

    -
    === Pluggable architecture
    -
    -There are cases where you have your contracts defined in other formats
    -like YAML, RAML or PACT. On the other hand you'd like to profit from
    -the test and stubs generation. It's really easy to add your own implementation
    -of either of those. Also you can customize the way tests are generated (for example you can generate
    -tests for other languages) and you can do the same for stubs generation (you can generate
    -stubs for other stub http server implementations).
    -
    -==== Custom contract converter
    -
    -Let's assume that your contract is written in a YAML file like this:
    -
    -[source,yml]
    -
    -
    -
    -

    request: +

    request:
       url: /foo
       method: PUT
       headers:
    @@ -7773,20 +7819,17 @@ response:
       headers:
         foo2: bar
       body:
    -    foo2: bar

    + foo2: bar
    +
    +
    +
    +

    Thanks to the interface

    -
    Thanks to the interface
    +
    package org.springframework.cloud.contract.spec
     
    -[source,groovy]
    -
    -
    -
    -

    package org.springframework.cloud.contract.spec

    -
    -
    -

    /** +/** * Converter to be used to convert FROM {@link File} TO {@link Contract} * and from {@link Contract} to {@code T} * @@ -7795,137 +7838,120 @@ response: * @author Marcin Grzejszczak * @since 1.1.0 */ -interface ContractConverter<T> {

    -
    -
    -
    -
    /**
    - * Should this file be accepted by the converter. Can use the file extension
    - * to check if the conversion is possible.
    - *
    - * @param file - file to be considered for conversion
    - * @return - {@code true} if the given implementation can convert the file
    - */
    -boolean isAccepted(File file)
    -
    -
    -
    -
    -
    /**
    - * Converts the given {@link File} to its {@link Contract} representation
    - *
    - * @param file - file to convert
    - * @return - {@link Contract} representation of the file
    - */
    -Collection<Contract> convertFrom(File file)
    -
    -
    -
    -
    -
    	/**
    +interface ContractConverter<T> {
    +
    +	/**
    +	 * Should this file be accepted by the converter. Can use the file extension
    +	 * to check if the conversion is possible.
    +	 *
    +	 * @param file - file to be considered for conversion
    +	 * @return - {@code true} if the given implementation can convert the file
    +	 */
    +	boolean isAccepted(File file)
    +
    +	/**
    +	 * Converts the given {@link File} to its {@link Contract} representation
    +	 *
    +	 * @param file - file to convert
    +	 * @return - {@link Contract} representation of the file
    +	 */
    +	Collection<Contract> convertFrom(File file)
    +
    +	/**
     	 * Converts the given {@link Contract} to a {@link T} representation
     	 *
     	 * @param contract - the parsed contract
     	 * @return - {@link T} the type to which we do the conversion
     	 */
     	T convertTo(Collection<Contract> contract)
    -}
    +}
    -
    -
    -
    you can register your own implementation of a contract structure converter.
    +
    +

    you can register your own implementation of a contract structure converter. Your implementation needs to state the condition on which it should start the -conversion. Also you have to define how to perform that conversion in both ways. - -IMPORTANT: Once you create your implementation you have to create a `/META-INF/spring.factories` +conversion. Also you have to define how to perform that conversion in both ways.

    +
    +
    +
    + + + + +
    +
    Important
    +
    +Once you create your implementation you have to create a /META-INF/spring.factories file in which you provide the fully qualified name of your implementation. - -Example of a `spring.factories` file - -[source] +
    - - - - - - -

    Converters

    -

    org.springframework.cloud.contract.spec.ContractConverter=\ -org.springframework.cloud.contract.verifier.converter.YamlContractConverter

    +

    Example of a spring.factories file

    -
    and the YAML implementation
    +
    # Converters
    +org.springframework.cloud.contract.spec.ContractConverter=\
    +org.springframework.cloud.contract.verifier.converter.YamlContractConverter
    +
    +
    +
    +

    and the YAML implementation

    +
    +
    +
    +
    package org.springframework.cloud.contract.verifier.converter
     
    -[source,groovy]
    -
    -
    -
    -

    package org.springframework.cloud.contract.verifier.converter

    -
    -
    -

    import groovy.transform.CompileStatic +import groovy.transform.CompileStatic import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractConverter import org.springframework.cloud.contract.spec.internal.Headers -import org.yaml.snakeyaml.Yaml

    -
    -
    -

    /** +import org.yaml.snakeyaml.Yaml + +/** * Simple converter from and to a {@link YamlContract} to a collection of {@link Contract} */ @CompileStatic -class YamlContractConverter implements ContractConverter<List<YamlContract>> {

    -
    -
    -
    -
    @Override
    -public boolean isAccepted(File file) {
    -	String name = file.getName()
    -	return name.endsWith(".yml") || name.endsWith(".yaml")
    -}
    -
    -
    -
    -
    -
    @Override
    -public Collection<Contract> convertFrom(File file) {
    -	try {
    -		YamlContract yamlContract = new Yaml().loadAs(new FileInputStream(file), YamlContract.class)
    -		return [Contract.make {
    -			request {
    -				method(yamlContract?.request?.method)
    -				url(yamlContract?.request?.url)
    -				headers {
    -					yamlContract?.request?.headers?.each { String key, Object value ->
    -						header(key, value)
    -					}
    -				}
    -				body(yamlContract?.request?.body)
    -			}
    -			response {
    -				status(yamlContract?.response?.status)
    -				headers {
    -					yamlContract?.response?.headers?.each { String key, Object value ->
    -						header(key, value)
    -					}
    -				}
    -				body(yamlContract?.response?.body)
    -			}
    -		}]
    +class YamlContractConverter implements ContractConverter<List<YamlContract>> {
    +
    +	@Override
    +	public boolean isAccepted(File file) {
    +		String name = file.getName()
    +		return name.endsWith(".yml") || name.endsWith(".yaml")
     	}
    -	catch (FileNotFoundException e) {
    -		throw new IllegalStateException(e)
    +
    +	@Override
    +	public Collection<Contract> convertFrom(File file) {
    +		try {
    +			YamlContract yamlContract = new Yaml().loadAs(new FileInputStream(file), YamlContract.class)
    +			return [Contract.make {
    +				request {
    +					method(yamlContract?.request?.method)
    +					url(yamlContract?.request?.url)
    +					headers {
    +						yamlContract?.request?.headers?.each { String key, Object value ->
    +							header(key, value)
    +						}
    +					}
    +					body(yamlContract?.request?.body)
    +				}
    +				response {
    +					status(yamlContract?.response?.status)
    +					headers {
    +						yamlContract?.response?.headers?.each { String key, Object value ->
    +							header(key, value)
    +						}
    +					}
    +					body(yamlContract?.response?.body)
    +				}
    +			}]
    +		}
    +		catch (FileNotFoundException e) {
    +			throw new IllegalStateException(e)
    +		}
     	}
    -}
    -
    -
    -
    -
    -
    	@Override
    +
    +	@Override
     	public List<YamlContract> convertTo(Collection<Contract> contracts) {
     		return contracts.collect { Contract contract ->
     			YamlContract yamlContract = new YamlContract()
    @@ -7943,27 +7969,25 @@ public Collection<Contract> convertFrom(File file) {
     			return yamlContract
     		}
     	}
    -}
    +}
    +
    +
    Pact converter
    +
    +

    Spring Cloud Contract comes with an out of the box support for Pact representation of contracts. +In other words instead of using the Groovy DSL you can use Pact files. In this section +we will present how to add such a support for your project.

    +
    +
    +
    Pact contract
    +
    +

    We will be working on the following example of a Pact contract. We’ve placed this file under +the src/test/resources/contracts folder.

    +
    -
    ===== Pact converter
    -
    -Spring Cloud Contract comes with an out of the box support for https://docs.pact.io/[Pact] representation of contracts.
    -In other words instead of using the Groovy DSL you can use Pact files. In this section
    -we will present how to add such a support for your project.
    -
    -====== Pact contract
    -
    -We will be working on the following example of a Pact contract. We've placed this file under
    -the `src/test/resources/contracts` folder.
    -
    -[source,javascript,indent=0]
    -
    -
    -
    -

    { +

    {
       "provider": {
         "name": "Provider"
       },
    @@ -8016,22 +8040,21 @@ the `src/test/resources/contracts` folder.
           "version": "2.4.18"
         }
       }
    -}

    -
    -
    -
    -
    ====== Pact for producers
    -
    -On the producer side you have add to your plugin configuration two additional dependencies.
    -One is the Spring Cloud Contract Pact support and the other represents the current
    -Pact version that you're using.
    -
    -[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
    -.Maven
    +}
    +
    +
    +
    Pact for producers
    -

    <plugin> +

    On the producer side you have add to your plugin configuration two additional dependencies. +One is the Spring Cloud Contract Pact support and the other represents the current +Pact version that you’re using.

    +
    +
    +
    Maven
    +
    +
    <plugin>
     	<groupId>org.springframework.cloud</groupId>
     	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
     	<version>${spring-cloud-contract.version}</version>
    @@ -8051,45 +8074,33 @@ Pact version that you're using.
     			<version>2.4.18</version>
     		</dependency>
     	</dependencies>
    -</plugin>

    +</plugin>
    -
    +
    +
    +
    Gradle
    -
    [source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
    -.Gradle
    +
    classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}"
    +classpath 'au.com.dius:pact-jvm-model:2.4.18'
    -

    classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}" -classpath 'au.com.dius:pact-jvm-model:2.4.18'

    +

    When you execute the build of your application a test, looking more or less like this, will be generated

    -
    When you execute the build of your application a test, looking more or less like this, will be generated
    -
    -[source,java,indent=0]
    -
    -
    -
    -
    -
    @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:
    +				.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
    +
    +	// when:
    +		ResponseOptions response = given().spec(request)
    +				.put("/fraudcheck");
    +
    +	// then:
     		assertThat(response.statusCode()).isEqualTo(200);
     		assertThat(response.header("Content-Type")).isEqualTo("application/vnd.fraud.v1+json;charset=UTF-8");
     	// and:
    @@ -8097,18 +8108,15 @@ public void validate_shouldMarkClientAsFraud() throws Exception {
     		assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high");
     	// and:
     		assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
    -}
    -
    -
    -
    -
    -
    and the stub looking like this
    -
    -[source,javascript,indent=0]
    +}
    -

    { +

    and the stub looking like this

    +
    +
    +
    +
    {
       "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
       "request" : {
         "url" : "/fraudcheck",
    @@ -8131,22 +8139,21 @@ public void validate_shouldMarkClientAsFraud() throws Exception {
           "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
         }
       }
    -}

    -
    -
    -
    -
    ====== Pact for consumers
    -
    -On the producer side you have add to your project dependencies two additional dependencies.
    -One is the Spring Cloud Contract Pact support and the other represents the current
    -Pact version that you're using.
    -
    -[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
    -.Maven
    +}
    +
    +
    +
    Pact for consumers
    -

    <dependency> +

    On the producer side you have add to your project dependencies two additional dependencies. +One is the Spring Cloud Contract Pact support and the other represents the current +Pact version that you’re using.

    +
    +
    +
    Maven
    +
    +
    <dependency>
     	<groupId>org.springframework.cloud</groupId>
     	<artifactId>spring-cloud-contract-spec-pact</artifactId>
     	<scope>test</scope>
    @@ -8156,51 +8163,46 @@ Pact version that you're using.
     	<artifactId>pact-jvm-model</artifactId>
     	<version>2.4.18</version>
     	<scope>test</scope>
    -</dependency>

    +</dependency>
    +
    +
    +
    +
    Gradle
    +
    +
    testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact"
    +testCompile 'au.com.dius:pact-jvm-model:2.4.18'
    +
    +
    +
    +
    +
    +
    +

    Custom test generator

    +
    +

    If you want to generate tests for different languages than Java or you’re +not happy with the way we’re building Java tests for you then you can register +your own implementation to do that.

    +
    +
    +

    Thanks to the interface

    -
    [source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
    -.Gradle
    -
    -
    -
    -

    testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact" -testCompile 'au.com.dius:pact-jvm-model:2.4.18'

    -
    -
    -
    -
    ==== Custom test generator
    +
    package org.springframework.cloud.contract.verifier.builder
     
    -If you want to generate tests for different languages than Java or you're
    -not happy with the way we're building Java tests for you then you can register
    -your own implementation to do that.
    -
    -Thanks to the interface
    -
    -[source,groovy]
    -
    -
    -
    -

    package org.springframework.cloud.contract.verifier.builder

    -
    -
    -

    import groovy.transform.CompileStatic +import groovy.transform.CompileStatic import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties -import org.springframework.cloud.contract.verifier.file.ContractMetadata

    -
    -
    -

    /** +import org.springframework.cloud.contract.verifier.file.ContractMetadata + +/** * Builds a single test. * * @since 1.1.0 */ @CompileStatic -interface SingleTestGenerator {

    -
    -
    -
    -
    	/**
    +interface SingleTestGenerator {
    +
    +	/**
     	 * Creates contents of a single test class in which all test scenarios from
     	 * the contract metadata should be placed.
     	 *
    @@ -8213,68 +8215,54 @@ interface SingleTestGenerator {

    */ String buildClass(ContractVerifierConfigProperties properties, Collection<ContractMetadata> listOfFiles, String className, String classPackage, String includedDirectoryRelativePath) -}
    +}
    +
    +

    you can register your own implementation that generates a test. Again, it’s enough to provide +a proper spring.factories file. Example:

    +
    -
    you can register your own implementation that generates a test. Again, it's enough to provide
    -a proper `spring.factories` file. Example:
    -
    -[source]
    +
    org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/
    +com.example.MyGenerator
    +
    +
    +

    Custom stub generator

    -

    org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/ -com.example.MyGenerator

    +

    If you want to generate stubs for other stub server than WireMock it’s enough to + plug in your own implementation of this interface:

    -
    ==== Custom stub generator
    +
    package org.springframework.cloud.contract.verifier.converter
     
    -If you want to generate stubs for other stub server than WireMock it's enough to
    - plug in your own implementation of this interface:
    -
    -[source,groovy]
    -
    -
    -
    -

    package org.springframework.cloud.contract.verifier.converter

    -
    -
    -

    import groovy.transform.CompileStatic +import groovy.transform.CompileStatic import org.springframework.cloud.contract.spec.Contract -import org.springframework.cloud.contract.verifier.file.ContractMetadata

    -
    -
    -

    /** +import org.springframework.cloud.contract.verifier.file.ContractMetadata + +/** * Converts contracts into their stub representation. * * @since 1.1.0 */ @CompileStatic -interface StubGenerator {

    -
    -
    -
    -
    /**
    - * Returns {@code true} if the converter can handle the file to convert it into a stub.
    - */
    -boolean canHandleFileName(String fileName)
    -
    -
    -
    -
    -
    /**
    - * Returns the collection of converted contracts into stubs. One contract can
    - * result in multiple stubs.
    - */
    -Map<Contract, String> convertContents(String rootName, ContractMetadata content)
    -
    -
    -
    -
    -
    	/**
    +interface StubGenerator {
    +
    +	/**
    +	 * Returns {@code true} if the converter can handle the file to convert it into a stub.
    +	 */
    +	boolean canHandleFileName(String fileName)
    +
    +	/**
    +	 * Returns the collection of converted contracts into stubs. One contract can
    +	 * result in multiple stubs.
    +	 */
    +	Map<Contract, String> convertContents(String rootName, ContractMetadata content)
    +
    +	/**
     	 * Returns the name of the converted stub file. If you have multiple contracts
     	 * in a single file then a prefix will be added to the generated file. If you
     	 * provide the {@link Contract#name} field then that field will override the
    @@ -8285,246 +8273,220 @@ Map<Contract, String> convertContents(String rootName, ContractMetadata co
     	 * converter will create two files {@code 0_foo.json} and {@code 1_foo.json}
     	 */
     	String generateOutputFileNameForInput(String inputFileName)
    -}
    +}
    -
    -
    -
    you can register your own implementation that generate Stubs. Again, it's enough to provide
    -a proper `spring.factories` file. Example:
    -
    -[source]
    -
    -
    -

    Stub converters

    -

    org.springframework.cloud.contract.verifier.converter.StubGenerator=\ -org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter

    +

    you can register your own implementation that generate Stubs. Again, it’s enough to provide +a proper spring.factories file. Example:

    -
    The default implementation is the WireMock stub generation.
    -
    -TIP: You can provide multiple stub generator implementations. That way for example from a single
    +
    # Stub converters
    +org.springframework.cloud.contract.verifier.converter.StubGenerator=\
    +org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter
    +
    +
    +
    +

    The default implementation is the WireMock stub generation.

    +
    +
    + + + + + +
    +
    Tip
    +
    +You can provide multiple stub generator implementations. That way for example from a single DSL as input you can e.g. produce WireMock stubs and Pact files too! - -==== Custom Stub Runner - -If you decide to have a custom stub generation you also need a custom way of running -stubs with your different stub provider. - -Let us assume that you're using https://github.com/dreamhead/moco[Moco] to build your stubs. -You wrote a proper stub generator and your stubs got placed 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. It can look like this: - -[source,groovy] +
    +
    +

    Custom Stub Runner

    +
    +

    If you decide to have a custom stub generation you also need a custom way of running +stubs with your different stub provider.

    +
    -

    package org.springframework.cloud.contract.stubrunner.provider.moco

    +

    Let us assume that you’re using Moco to build your stubs. +You wrote a proper stub generator and your stubs got placed in a JAR file.

    -

    import com.github.dreamhead.moco.bootstrap.arg.HttpArgs +

    In order for Stub Runner to know how to run your stubs you have to define a custom + HTTP Stub server implementation. It can look like this:

    +
    +
    +
    +
    package org.springframework.cloud.contract.stubrunner.provider.moco
    +
    +import com.github.dreamhead.moco.bootstrap.arg.HttpArgs
     import com.github.dreamhead.moco.runner.JsonRunner
     import org.springframework.cloud.contract.stubrunner.HttpServerStub
    -import org.springframework.util.SocketUtils

    -
    -
    -

    class MocoHttpServerStub implements HttpServerStub {

    -
    -
    -
    -
    private boolean started
    -private JsonRunner runner
    -private int port
    -
    -
    -
    -
    -
    @Override
    -int port() {
    -	if (!isRunning()) {
    -		return -1
    +import org.springframework.util.SocketUtils
    +
    +class MocoHttpServerStub implements HttpServerStub {
    +
    +	private boolean started
    +	private JsonRunner runner
    +	private int port
    +
    +	@Override
    +	int port() {
    +		if (!isRunning()) {
    +			return -1
    +		}
    +		return port
     	}
    -	return port
    -}
    -
    -
    -
    -
    -
    @Override
    -boolean isRunning() {
    -	return started
    -}
    -
    -
    -
    -
    -
    @Override
    -HttpServerStub start() {
    -	return start(SocketUtils.findAvailableTcpPort())
    -}
    -
    -
    -
    -
    -
    @Override
    -HttpServerStub start(int port) {
    -	this.port = port
    -	return this
    -}
    -
    -
    -
    -
    -
    @Override
    -HttpServerStub stop() {
    -	if (!isRunning()) {
    +
    +	@Override
    +	boolean isRunning() {
    +		return started
    +	}
    +
    +	@Override
    +	HttpServerStub start() {
    +		return start(SocketUtils.findAvailableTcpPort())
    +	}
    +
    +	@Override
    +	HttpServerStub start(int port) {
    +		this.port = port
     		return this
     	}
    -	this.runner.stop()
    -	return this
    -}
    -
    -
    -
    -
    -
    @Override
    -HttpServerStub registerMappings(Collection<File> stubFiles) {
    -	List<InputStream> streams = stubFiles.collect { it.newInputStream() }
    -	this.runner = JsonRunner.newJsonRunnerWithStreams(streams,
    -			HttpArgs.httpArgs().withPort(this.port).build())
    -	this.runner.run()
    -	this.started = true
    -	return this
    -}
    -
    -
    -
    -
    -
    	@Override
    +
    +	@Override
    +	HttpServerStub stop() {
    +		if (!isRunning()) {
    +			return this
    +		}
    +		this.runner.stop()
    +		return this
    +	}
    +
    +	@Override
    +	HttpServerStub registerMappings(Collection<File> stubFiles) {
    +		List<InputStream> streams = stubFiles.collect { it.newInputStream() }
    +		this.runner = JsonRunner.newJsonRunnerWithStreams(streams,
    +				HttpArgs.httpArgs().withPort(this.port).build())
    +		this.runner.run()
    +		this.started = true
    +		return this
    +	}
    +
    +	@Override
     	boolean isAccepted(File file) {
     		return file.name.endsWith(".json")
     	}
    -}
    +}
    -
    -
    -
    and just register it in your `spring.factories` file
    -
    -[source]
    -
    -
    -

    Example of a custom HTTP Server Stub

    -

    org.springframework.cloud.contract.stubrunner.HttpServerStub=\ -org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub

    +

    and just register it in your spring.factories file

    -
    that way you'll be able to run stubs using Moco.
    -
    -IMPORTANT: If you don't provide any implementation then the default one - WireMock based
    +
    # Example of a custom HTTP Server Stub
    +org.springframework.cloud.contract.stubrunner.HttpServerStub=\
    +org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub
    +
    +
    +
    +

    that way you’ll be able to run stubs using Moco.

    +
    +
    + + + + + +
    +
    Important
    +
    +If you don’t provide any implementation then the default one - WireMock based will be picked. If you provide more than one then the first one on the list will be picked. - -==== Custom Stub Downloader - -You can customize the way your stubs are downloaded. If you don't want to download the JARs +
    +
    +
    +
    +

    Custom Stub Downloader

    +
    +

    You can customize the way your stubs are downloaded. If you don’t want to download the JARs from Nexus / Artifactory in the way we do by default you can set your own implementation. -Below you can find an example of a Stub Downloader Provider that takes `json` files from the test resources +Below you can find an example of a Stub Downloader Provider that takes json files from the test resources from classpath, copies them to a temp file and then passes that temporary folder - as a root for the stubs. + as a root for the stubs.

    +
    +
    +
    +
    package org.springframework.cloud.contract.stubrunner.provider.moco
     
    -[source,java]
    -
    -
    -
    -

    package org.springframework.cloud.contract.stubrunner.provider.moco

    -
    -
    -

    import org.springframework.cloud.contract.stubrunner.StubConfiguration +import org.springframework.cloud.contract.stubrunner.StubConfiguration import org.springframework.cloud.contract.stubrunner.StubDownloader import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder import org.springframework.cloud.contract.stubrunner.StubRunnerOptions import org.springframework.core.io.DefaultResourceLoader import org.springframework.core.io.Resource -import org.springframework.core.io.support.PathMatchingResourcePatternResolver

    -
    -
    -

    import java.nio.file.Files

    -
    -
    -

    /** - * Poor man’s version of taking stubs from classpath. It needs much more +import org.springframework.core.io.support.PathMatchingResourcePatternResolver + +import java.nio.file.Files + +/** + * Poor man's version of taking stubs from classpath. It needs much more * love and attention to go to the main sources. * * @author Marcin Grzejszczak */ -class ClasspathStubProvider implements StubDownloaderBuilder {

    -
    -
    -
    -
    private static final int TEMP_DIR_ATTEMPTS = 10000
    -
    -
    -
    -
    -
    @Override
    -public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
    -	final StubConfiguration configuration = stubRunnerOptions.getDependencies().first()
    -	PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
    -			new DefaultResourceLoader())
    -	try {
    -		String rootFolder = repoRoot(stubRunnerOptions) ?: "**/" + separatedArtifact(configuration) + "/**/*.json"
    -		Resource[] resources = resolver.getResources(rootFolder)
    -		final File tmp = createTempDir()
    -		tmp.deleteOnExit()
    -		// you'd have to write an impl to maintain the folder structure
    -		// this is just for demo
    -		resources.each { Resource resource ->
    -			Files.copy(resource.getInputStream(), new File(tmp, resource.getFile().getName()).toPath())
    -		}
    -		return new StubDownloader() {
    -			@Override
    -			public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
    -					StubConfiguration stubConfiguration) {
    -				return new AbstractMap.SimpleEntry(configuration, tmp)
    +class ClasspathStubProvider implements StubDownloaderBuilder {
    +
    +	private static final int TEMP_DIR_ATTEMPTS = 10000
    +
    +	@Override
    +	public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
    +		final StubConfiguration configuration = stubRunnerOptions.getDependencies().first()
    +		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
    +				new DefaultResourceLoader())
    +		try {
    +			String rootFolder = repoRoot(stubRunnerOptions) ?: "**/" + separatedArtifact(configuration) + "/**/*.json"
    +			Resource[] resources = resolver.getResources(rootFolder)
    +			final File tmp = createTempDir()
    +			tmp.deleteOnExit()
    +			// you'd have to write an impl to maintain the folder structure
    +			// this is just for demo
    +			resources.each { Resource resource ->
    +				Files.copy(resource.getInputStream(), new File(tmp, resource.getFile().getName()).toPath())
     			}
    +			return new StubDownloader() {
    +				@Override
    +				public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
    +						StubConfiguration stubConfiguration) {
    +					return new AbstractMap.SimpleEntry(configuration, tmp)
    +				}
    +			}
    +		} catch (IOException e) {
    +			throw new IllegalStateException(e)
     		}
    -	} catch (IOException e) {
    -		throw new IllegalStateException(e)
     	}
    -}
    -
    -
    -
    -
    -
    private String repoRoot(StubRunnerOptions stubRunnerOptions) {
    -	switch (stubRunnerOptions.stubRepositoryRoot) {
    -		case { !it }:
    -			return ""
    -		case { String root -> root.endsWith("**/*.json") }:
    -			return stubRunnerOptions.stubRepositoryRoot
    -		default:
    -			return stubRunnerOptions.stubRepositoryRoot + "/**/*.json"
    +
    +	private String repoRoot(StubRunnerOptions stubRunnerOptions) {
    +		switch (stubRunnerOptions.stubRepositoryRoot) {
    +			case { !it }:
    +				return ""
    +			case { String root -> root.endsWith("**/*.json") }:
    +				return stubRunnerOptions.stubRepositoryRoot
    +			default:
    +				return stubRunnerOptions.stubRepositoryRoot + "/**/*.json"
    +		}
     	}
    -}
    -
    -
    -
    -
    -
    private String separatedArtifact(StubConfiguration configuration) {
    -	return configuration.getGroupId().replace(".", File.separator) +
    -			File.separator + configuration.getArtifactId()
    -}
    -
    -
    -
    -
    -
    	// Taken from Guava
    +
    +	private String separatedArtifact(StubConfiguration configuration) {
    +		return configuration.getGroupId().replace(".", File.separator) +
    +				File.separator + configuration.getArtifactId()
    +	}
    +
    +	// Taken from Guava
     	private File createTempDir() {
     		File baseDir = new File(System.getProperty("java.io.tmpdir"))
     		String baseName = System.currentTimeMillis() + "-"
    @@ -8538,40 +8500,71 @@ public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
     				"Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (
     						TEMP_DIR_ATTEMPTS - 1) + ")")
     	}
    -}
    +}
    -
    -
    -
    and just register it in your `spring.factories` file
    -
    -[source]
    -
    -
    -

    Example of a custom Stub Downloader Provider

    -

    org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\ -org.springframework.cloud.contract.stubrunner.provider.moco.ClasspathStubProvider

    +

    and just register it in your spring.factories file

    -
    that way you'll be able to pick a folder with the source of your stubs.
    -
    -IMPORTANT: If you don't provide any implementation then the default one - Aether based that will download stubs from a remote repo
    +
    # Example of a custom Stub Downloader Provider
    +org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
    +org.springframework.cloud.contract.stubrunner.provider.moco.ClasspathStubProvider
    +
    +
    +
    +

    that way you’ll be able to pick a folder with the source of your stubs.

    +
    +
    + + + + + +
    +
    Important
    +
    +If you don’t provide any implementation then the default one - Aether based that will download stubs from a remote repo will be picked. If you provide more than one then the first one on the list will be picked. - -=== Links - -Here you can find interesting links related to Spring Cloud Contract Verifier: - -- 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://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html[Spring Cloud Contract Documentation] -- https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html/deprecated[Accurest Legacy Documentation] -- https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html/#spring-cloud-contract-stub-runner[Spring Cloud Contract Stub Runner Documentation] -- https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html/#stub-runner-for-messaging[Spring Cloud Contract Stub Runner Messaging Documentation] -- https://gitter.im/spring-cloud/spring-cloud-contract[Spring Cloud Contract Gitter] -- https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract-maven-plugin/[Spring Cloud Contract Maven Plugin] +
    +
    +
    +
    +
    + +
    +

    Here you can find interesting links related to Spring Cloud Contract Verifier:

    +
    + +