How Consumer‑Driven Contract Testing Transforms Distributed Microservice Development with Spring Cloud Contract
This article explains the shortcomings of traditional distributed microservice testing, introduces consumer‑driven contract testing, outlines the evolution of a distributed R&D model, and provides a step‑by‑step guide with code samples for implementing Spring Cloud Contract in both provider and consumer services.
Distributed R&D model and contract testing
In a microservice environment each team owns a service. Bugs are often discovered only during integration testing because the service is first deployed to a test environment. To surface defects earlier, unit tests are added (EasyMock, Mockito) and external dependencies are mocked (Docker containers for DB, Redis). When a provider changes its API, consumers may break, causing production incidents. Consumer‑driven contract testing solves this by defining a contract that both provider and consumer agree on and writing unit tests against it.
Spring Cloud Contract
Spring Cloud Contract (SCC) is used to define these contracts. A contract describes request parameters and expected responses. SCC can generate WireMock stubs, verifier tests and integrates with Spring Cloud.
Used for unit testing
Defines remote service data
Automatically generates test code
Implementation steps
Data definition side
All request/response definitions are placed in a dedicated spring-cloud-contract module (internal name). The contract is written in Groovy DSL; normally the service developer writes it, but a consumer can also author it and have the provider review.
Service provider
Add the following test‑scope Maven dependencies to the provider module:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.yonghui</groupId>
<artifactId>spring-cloud-contract</artifactId>
<version>1.0‑SNAPSHOT</version>
</dependency>Configure the spring-cloud-contract-maven-plugin to generate verifier tests and stubs:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>1.1.4.RELEASE</version>
<extensions>true</extensions>
<configuration>
<contractsWorkOffline>true</contractsWorkOffline>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*</contractPackageRegex>
<baseClassFQN>contract.ContractBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
<basePackageForTests>verifier.tests</basePackageForTests>
<contractDependency>
<groupId>com.yonghui</groupId>
<artifactId>spring-cloud-contract</artifactId>
<version>1.0‑SNAPSHOT</version>
<classifier>stubs</classifier>
</contractDependency>
<contractsPath>contracts/xxx-mst-center</contractsPath>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.12</version>
</dependency>
<dependency>
<groupId>com.yonghui</groupId>
<artifactId>spring-cloud-contract</artifactId>
<version>1.0‑SNAPSHOT</version>
<classifier>stubs</classifier>
</dependency>
</dependencies>
</plugin>Base class for generated tests can initialise an embedded MySQL and load SQL fixtures. Example:
package contract.resources;
import com.yonghui.junit.InmomeryDbResource;
public class LocationDbResource extends InmomeryDbResource {
public LocationDbResource() {
super(40200, "xxx_mst_center");
}
@Override
protected void before() throws Throwable {
super.before();
runResourceFile(dbName, "sql/contract/mst_location.sql");
}
}Bootstrap class used by the provider tests (Consul and other cloud components are disabled to keep the test lightweight):
package com.yonghui.xxx.mst.center.api.impl;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@EnableAutoConfiguration
@Import({FeignConfiguration.class})
@ComponentScan(basePackages = "com.yonghui.xxx")
@MapperScan("com.yonghui.xxx.mst.center.mapper")
public class Bootstrap {
private static final Logger log = LoggerFactory.getLogger(Bootstrap.class);
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class, args);
log.info("Bootstrap started successfully");
}
}Test resources must disable Consul to avoid external service discovery:
spring.cloud.consul.enabled=false
spring.application.feature.enabled=falseService consumer
The consumer uses the generated stubs via @AutoConfigureStubRunner. The annotation must reference the correct artifact, version and port.
@AutoConfigureStubRunner(
ids = {"com.yonghui:xxx-mst-center-server:1.0-SNAPSHOT:stubs:5656"},
workOffline = true)Example consumer test that starts an embedded MySQL and Redis, loads the Spring context and configures RestAssuredMockMvc:
package contract;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
import com.yonghui.junit.InmomeryDbResource;
import com.yonghui.junit.RedisResource;
import com.yonghui.xxx.inventory.center.api.impl.TestBootstrap;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.rules.ExternalResource;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestBootstrap.class})
@AutoConfigureStubRunner(
ids = {"com.yonghui:xxx-mst-center-server:1.0-SNAPSHOT:stubs:5656"},
workOffline = true)
public class XxxInventoryCenterServiceBase extends InmomeryDbResource {
@Autowired
private WebApplicationContext context;
@ClassRule
public static final ExternalResource redis = new RedisResource(20300);
public XxxInventoryCenterServiceBase() {
super(40200, "xxx_inventory_center");
}
@Before
public void setUp() throws Throwable {
RestAssuredMockMvc.webAppContextSetup(context);
super.before();
}
}Running mvn clean install -Dmaven.test.skip=false generates the verifier tests under target/generated-test-sources. Use a plugin version greater than 1.1.4.RELEASE to avoid a known DSL long‑type bug.
The three‑party model (data definition project, provider, consumer) centralises contract definitions, enables automatic stub generation, and guarantees that both sides verify the contract during unit testing, reducing integration‑time failures.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
