In order to use Spring Cloud Contract Verifier with WireMock you have to use Gradle or Maven plugin.
Warning | |
---|---|
If you want to use Spock in your projects you have to add separately
the |
buildscript { repositories { mavenCentral() } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}" classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifier_version}" } } apply plugin: 'groovy' apply plugin: 'spring-cloud-contract' dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifier_version}" } } dependencies { testCompile 'org.codehaus.groovy:groovy-all:2.4.6' // example with adding Spock core and Spock Spring testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4' testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' }
By default Rest Assured 2.x is added to the classpath. However in order to give the users the opportunity to use Rest Assured 3.x it’s enough to add it to the plugins classpath.
buildscript { repositories { mavenCentral() } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}" classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifier_version}" classpath "io.rest-assured:rest-assured:3.0.2" classpath "io.rest-assured:spring-mock-mvc:3.0.2" } } depenendencies { // all dependencies // you can exclude rest-assured from spring-cloud-contract-verifier testCompile "io.rest-assured:rest-assured:3.0.2" testCompile "io.rest-assured:spring-mock-mvc:3.0.2" }
That way the plugin will automatically see that Rest Assured 3.x is present on the classpath and will modify the imports accordingly.
Add the additional snapshot repository to your build.gradle to use snapshot versions which are automatically uploaded after every successful build:
buildscript { repositories { mavenCentral() mavenLocal() maven { url "https://repo.spring.io/snapshot" } maven { url "https://repo.spring.io/milestone" } maven { url "https://repo.spring.io/release" } } }
By default Spring Cloud Contract Verifier is looking for stubs in src/test/resources/contracts
directory.
Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test. We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name. So with following structure
src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy
Spring Cloud Contract Verifier will create test class defaultBasePackage.MyService
with two methods
shouldCreateUser()
shouldReturnUser()
Plugin registers itself to be invoked before check
task. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateContractTests
task.
Default Gradle Plugin setup creates the following Gradle part of the build (it’s a pseudocode)
contracts { targetFramework = 'JUNIT' testMode = 'MockMvc' generatedTestSourcesDir = project.file("${project.buildDir}/generated-test-sources/contracts") contractsDslDir = "${project.rootDir}/src/test/resources/contracts" basePackageForTests = 'org.springframework.cloud.verifier.tests' stubsOutputDir = project.file("${project.buildDir}/stubs") // the following properties are used when you want to provide where the JAR with contract lays contractDependency { stringNotation = '' } contractsPath = '' contractsWorkOffline = false contractRepository { cacheDownloadedContracts(true) } } tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') { baseName = project.name classifier = contracts.stubsSuffix from contractVerifier.stubsOutputDir } project.artifacts { archives task } tasks.create(type: Copy, name: 'copyContracts') { from contracts.contractsDslDir into contracts.stubsOutputDir } verifierStubsJar.dependsOn 'copyContracts' publishing { publications { stubs(MavenPublication) { artifactId project.name artifact verifierStubsJar } } }
To change default configuration just add contracts
snippet to your Gradle config
contracts { testMode = 'MockMvc' baseClassForTests = 'org.mycompany.tests' generatedTestSourcesDir = project.file('src/generatedContract') }
spock.lang.Specification
if using Spock tests.$rootDir/src/test/resources/contracts
$buildDir/generated-test-sources/contractVerifier
The following properties are used when you want to provide where the JAR with contract lays
groupid:artifactid:version:classifier
coordinates. You can use the contractDependency
closure to set it upgroupid/artifactid
where groupid
will be slash separated. Otherwise will scan contracts under provided directoryWhen using Spring Cloud Contract Verifier in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.
abstract class BaseMockMvcSpec extends Specification { def setup() { RestAssuredMockMvc.standaloneSetup(new PairIdController()) } void isProperCorrelationId(Integer correlationId) { assert correlationId == 123456 } void isEmpty(String value) { assert value == null } }
In case of using Explicit
mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT
mode this base class
should also contain protected WebTarget webTarget
field, right now the only option to test JAX-RS API is to start a web server.
If your base classes differ between contracts you can tell the Spring Cloud Contract plugin which class should get extended by the autogenerated tests. You have two options:
packageWithBaseClasses
baseClassMappings
Convention
The convention is such that if you have a contract under e.g. src/test/resources/contract/foo/bar/baz/
and provide the value of the packageWithBaseClasses
property
to com.example.base
then we will assume that there is a BarBazBase
class under com.example.base
package. In other words we take last two parts of package
if they exist and form a class with a Base
suffix. Takes precedence over baseClassForTests. Example of usage in the contracts
closure:
packageWithBaseClasses = 'com.example.base'
Mapping
You can manually map a regular expression of the contract’s package to fully qualified name of the base class for the matched contract. Let’s take a look at the following example:
baseClassForTests = "com.example.FooBase" baseClassMappings { baseClassMapping('.*/com/.*', 'com.example.ComBase') baseClassMapping('.*/bar/.*':'com.example.BarBase') }
Let’s assume that you have contracts under
- src/test/resources/contract/com/
- src/test/resources/contract/foo/
By providing the baseClassForTests
we have a fallback in case mapping didn’t succeed (you could also provide
the packageWithBaseClasses
as fallback). That way the tests generated from src/test/resources/contract/com/
contracts
will be extending the com.example.ComBase
whereas the rest of tests will extend com.example.FooBase
.
To ensure that provider side is complaint with defined contracts, you need to invoke:
./gradlew generateContractTests test
In consumer service you need to configure Spring Cloud Contract Verifier plugin in exactly the same way as in case of provider. If you don’t want to use Stub Runner then you need to copy contracts stored in
src/test/resources/contracts
and generate WireMock json stubs using:
./gradlew generateClientStubs
Note that stubsOutputDir
option has to be set for stub generation to work.
When present, json stubs can be used in consumer automated tests.
@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application) class LoanApplicationServiceSpec extends Specification { @ClassRule @Shared WireMockClassRule wireMockRule == new WireMockClassRule() @Autowired LoanApplicationService sut def 'should successfully apply for loan'() { given: LoanApplication application = new LoanApplication(client: new Client(clientPesel: '12345678901'), amount: 123.123) when: LoanApplicationResult loanApplication == sut.loanApplication(application) then: loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLIED loanApplication.rejectionReason == null } }
Underneath LoanApplication makes a call to FraudDetection service. This request is handled by WireMock server configured using stubs generated by Spring Cloud Contract Verifier.
Add the Spring Cloud Contract BOM
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Next, the Spring Cloud Contract Verifier
Maven plugin
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses> </configuration> </plugin>
You can read more in the Spring Cloud Contract Maven Plugin Docs
By default Rest Assured 2.x is added to the classpath. However in order to give the users the opportunity to use Rest Assured 3.x it’s enough to add it to the plugins classpath.
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <packageWithBaseClasses>com.example</packageWithBaseClasses> </configuration> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-verifier</artifactId> <version>${spring-cloud-contract.version}</version> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>3.0.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-mock-mvc</artifactId> <version>3.0.2</version> <scope>compile</scope> </dependency> </dependencies> </plugin> <dependencies> <!-- all dependencies --> <!-- you can exclude rest-assured from spring-cloud-contract-verifier --> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>3.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-mock-mvc</artifactId> <version>3.0.2</version> <scope>test</scope> </dependency> </dependencies>
That way the plugin will automatically see that Rest Assured 3.x is present on the classpath and will modify the imports accordingly.
For Snapshot / Milestone versions you have to add the following section to your pom.xml
<repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories>
By default Spring Cloud Contract Verifier is looking for stubs in src/test/resources/contracts
directory.
Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test.
We assume that it contains at least one directory which will be used as test class name. If there is more than one level of nested directories all except the last one will be used as package name.
So with following structure
src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy
Spring Cloud Contract Verifier will create test class defaultBasePackage.MyService
with two methods
- shouldCreateUser()
- shouldReturnUser()
Plugin goal generateTests
is assigned to be invoked in phase generate-test-sources
. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateTests
goal.
To change default configuration just add configuration
section to plugin definition or execution
definition.
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <executions> <execution> <goals> <goal>convert</goal> <goal>generateStubs</goal> <goal>generateTests</goal> </goals> </execution> </executions> <configuration> <basePackageForTests>org.springframework.cloud.verifier.twitter.place</basePackageForTests> <baseClassForTests>org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec</baseClassForTests> </configuration> </plugin>
MockMvc
which is based on Spring’s MockMvc. It can also be changed to JaxRsClient
or to Explicit
for real HTTP calls.org.springframework.cloud.verifier.tests
.spock.lang.Specification
if using Spock tests./src/test/resources/contracts
.src/test/resources/contract/foo/bar/baz/
and provide the value of this property
to com.example.base
then we will assume that there is a BarBazBase
class under com.example.base
package. Takes precedence
over baseClassForTestscontractPackageRegex
which is checked
against the package in which the contract lays and baseClassFQN
that maps to fully qualified name of the base class for the matched
contract. If you have a contract under src/test/resources/contract/foo/bar/baz/
and map the property .*
→ com.example.base.BaseClass
then
the test class generated from these contracts will extend com.example.base.BaseClass
. Takes precedence over packageWithBaseClasses
and baseClassForTests.If you want to download your contract definitions from a Maven repository you can use
groupid/artifactid
where gropuid
is slash separated.contractRepository
closure - URL to a repo with the artifacts with contracts, if not provided should use the current Maven onescontractRepository - closure where you can define properties related to repository with contracts
+
or 1.0.0.BUILD-SNAPSHOT
won’t get cached).
By default this feature is turned on.When using Spring Cloud Contract Verifier in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.
package org.mycompany.tests import org.mycompany.ExampleSpringController import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc import spock.lang.Specification class MvcSpec extends Specification { def setup() { RestAssuredMockMvc.standaloneSetup(new ExampleSpringController()) } }
In case of using Explicit
mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT
mode this base class should also contain protected WebTarget webTarget
field, right now the only option to test JAX-RS API is to start a web server.
If your base classes differ between contracts you can tell the Spring Cloud Contract plugin which class should get extended by the autogenerated tests. You have two options:
packageWithBaseClasses
baseClassMappings
Convention
The convention is such that if you have a contract under e.g. src/test/resources/contract/hello/v1/
and provide the value of the packageWithBaseClasses
property
to hello
then we will assume that there is a HelloV1Base
class under hello
package. In other words we take last two parts of package
if they exist and form a class with a Base
suffix. Takes precedence over baseClassForTests. Example of usage:
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <packageWithBaseClasses>hello</packageWithBaseClasses> </configuration> </plugin>
Mapping
You can manually map a regular expression of the contract’s package to fully qualified name of the base class for the matched contract.
You have to provide a list baseClassMappings
of baseClassMapping
that takes a contractPackageRegex
to baseClassFQN
mapping.
Let’s take a look at the following example:
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <baseClassForTests>com.example.FooBase</baseClassForTests> <baseClassMappings> <baseClassMapping> <contractPackageRegex>.*com.*</contractPackageRegex> <baseClassFQN>com.example.TestBase</baseClassFQN> </baseClassMapping> </baseClassMappings> </configuration> </plugin>
Let’s assume that you have contracts under
- src/test/resources/contract/com/
- src/test/resources/contract/foo/
By providing the baseClassForTests
we have a fallback in case mapping didn’t succeed (you could also provide
the packageWithBaseClasses
as fallback). That way the tests generated from src/test/resources/contract/com/
contracts
will be extending the com.example.ComBase
whereas the rest of tests will extend com.example.FooBase
.
Spring Cloud Contract Maven Plugin generates verification code into directory /generated-test-sources/contractVerifier
and attach this directory to testCompile
goal.
For Groovy Spock code use:
<plugin> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> <version>1.5</version> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <testSources> <testSource> <directory>${project.basedir}/src/test/groovy</directory> <includes> <include>**/*.groovy</include> </includes> </testSource> <testSource> <directory>${project.build.directory}/generated-test-sources/contractVerifier</directory> <includes> <include>**/*.groovy</include> </includes> </testSource> </testSources> </configuration> </plugin>
To ensure that provider side is complaint with defined contracts, you need to invoke mvn generateTest test
In case you see the following exception while using STS
when you click on the marker you should see sth like this
plugin:1.1.0.M1:convert:default-convert:process-test-resources) org.apache.maven.plugin.PluginExecutionException: Execution default-convert of goal org.springframework.cloud:spring- cloud-contract-maven-plugin:1.1.0.M1:convert failed. at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:145) at org.eclipse.m2e.core.internal.embedder.MavenImpl.execute(MavenImpl.java:331) at org.eclipse.m2e.core.internal.embedder.MavenImpl$11.call(MavenImpl.java:1362) at ... org.eclipse.core.internal.jobs.Worker.run(Worker.java:55) Caused by: java.lang.NullPointerException at org.eclipse.m2e.core.internal.builder.plexusbuildapi.EclipseIncrementalBuildContext.hasDelta(EclipseIncrementalBuildContext.java:53) at org.sonatype.plexus.build.incremental.ThreadBuildContext.hasDelta(ThreadBuildContext.java:59) at
In order to fix this issue just provide the following section in your pom.xml
<build> <pluginManagement> <plugins> <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. --> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <versionRange>[1.0,)</versionRange> <goals> <goal>convert</goal> </goals> </pluginExecutionFilter> <action> <execute /> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement> </build>
You can actually use the Spring Cloud Contract Verifier also for the consumer side!
You can use the plugin so that it only converts the contracts and generates the stubs.
To achieve that you need to configure Spring Cloud Contract Verifier plugin in exactly
the same way as in case of provider. You need to copy contracts stored in
src/test/resources/contracts
and generate WireMock json stubs using:
mvn generateStubs
command. By default generated WireMock mapping is
stored in directory target/mappings
. Your project should create from
this generated mappings additional artifact with classifier stubs
for
easy deploy to maven repository.
Sample configuration:
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${verifier-plugin.version}</version> <executions> <execution> <goals> <goal>convert</goal> <goal>generateStubs</goal> </goals> </execution> </executions> </plugin>
When present, json stubs can be used in consumer automated tests.
@RunWith(SpringTestRunner.class) @SpringBootTest @AutoConfigureStubRunner public class LoanApplicationServiceTests { @Autowired LoanApplicationService service; @Test public void shouldSuccessfullyApplyForLoan() { //given: LoanApplication application = new LoanApplication(new Client("12345678901"), 123.123); //when: LoanApplicationResult loanApplication = service.loanApplication(application); // then: assertThat(loanApplication.loanApplicationStatus).isEqualTo(LoanApplicationStatus.LOAN_APPLIED); assertThat(loanApplication.rejectionReason).isNull(); } }
Underneath LoanApplication
makes a call to the FraudDetection
service. This request is handled by
a WireMock server configured using stubs generated by Spring Cloud Contract Verifier.
It’s possible to handle scenarios with Spring Cloud Contract Verifier. All you need to do is to stick to proper naming convention while creating your contracts. The convention requires to include order number followed by the underscore.
my_contracts_dir\ scenario1\ 1_login.groovy 2_showCart.groovy 3_logout.groovy
Such tree will cause Spring Cloud Contract Verifier generating WireMock’s scenario with name scenario1
and three steps:
Started
pointing to:Step1
pointing to:Step2
which will close the scenario.More details about WireMock scenarios can be found under http://wiremock.org/stateful-behaviour.html
Spring Cloud Contract Verifier will also generate tests with guaranteed order of execution.
The Maven and Gradle plugin that we’re created are adding the tasks that create the stubs jar for you. What can be problematic is that when reusing the stubs you can by mistake import all of that stub dependencies! When building a Maven artifact even though you have a couple of different jars, all of them share one pom:
├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar ├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar.sha1 ├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar ├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar.sha1 ├── github-webhook-0.0.1.BUILD-SNAPSHOT.jar ├── github-webhook-0.0.1.BUILD-SNAPSHOT.pom ├── github-webhook-0.0.1.BUILD-SNAPSHOT-stubs.jar ├── ... └── ...
There are three possibilities of working with those dependencies so as not to have any issues with transitive dependencies.
Mark all application dependencies as optional
If in the github-webhook
application we would mark all of our dependencies as optional, when you include the
github-webhook
stubs in another application (or when that dependency gets downloaded by Stub Runner) then, since
all of the depenencies are optional, they will not get downloaded.
Create a separate artifactid for stubs
If you create a separate artifactid then you can set it up in whatever way you wish. For example by having no dependencies at all.
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.